mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-21 23:00:42 -08:00
212 lines
5.2 KiB
Go
212 lines
5.2 KiB
Go
package analyzer
|
|
|
|
import (
|
|
"bufio"
|
|
"compress/gzip"
|
|
"context"
|
|
"io"
|
|
"os"
|
|
|
|
"github.com/aquasecurity/fanal/utils"
|
|
|
|
"github.com/aquasecurity/fanal/types"
|
|
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/aquasecurity/fanal/extractor"
|
|
godeptypes "github.com/aquasecurity/go-dep-parser/pkg/types"
|
|
)
|
|
|
|
var (
|
|
osAnalyzers []OSAnalyzer
|
|
pkgAnalyzers []PkgAnalyzer
|
|
libAnalyzers []LibraryAnalyzer
|
|
commandAnalyzers []CommandAnalyzer
|
|
additionalFiles []string
|
|
|
|
// ErrUnknownOS occurs when unknown OS is analyzed.
|
|
ErrUnknownOS = xerrors.New("Unknown OS")
|
|
// ErrPkgAnalysis occurs when the analysis of packages is failed.
|
|
ErrPkgAnalysis = xerrors.New("Failed to analyze packages")
|
|
// ErrNoPkgsDetected occurs when the required files for an OS package manager are not detected
|
|
ErrNoPkgsDetected = xerrors.New("No packages detected")
|
|
)
|
|
|
|
type Config struct {
|
|
Extractor extractor.Extractor
|
|
}
|
|
|
|
type OSAnalyzer interface {
|
|
Analyze(extractor.FileMap) (OS, error)
|
|
RequiredFiles() []string
|
|
}
|
|
|
|
type PkgAnalyzer interface {
|
|
Analyze(extractor.FileMap) ([]Package, error)
|
|
RequiredFiles() []string
|
|
}
|
|
|
|
type CommandAnalyzer interface {
|
|
Analyze(OS, extractor.FileMap) ([]Package, error)
|
|
RequiredFiles() []string
|
|
}
|
|
|
|
type FilePath string
|
|
|
|
type LibraryAnalyzer interface {
|
|
Analyze(extractor.FileMap) (map[FilePath][]godeptypes.Library, error)
|
|
RequiredFiles() []string
|
|
}
|
|
|
|
type OS struct {
|
|
Name string
|
|
Family string
|
|
}
|
|
|
|
type Package struct {
|
|
Name string
|
|
Version string
|
|
Release string
|
|
Epoch int
|
|
Arch string
|
|
SrcName string
|
|
SrcVersion string
|
|
SrcRelease string
|
|
SrcEpoch int
|
|
}
|
|
|
|
type SrcPackage struct {
|
|
Name string `json:"name"`
|
|
Version string `json:"version"`
|
|
BinaryNames []string `json:"binaryNames"`
|
|
}
|
|
|
|
func RegisterOSAnalyzer(analyzer OSAnalyzer) {
|
|
osAnalyzers = append(osAnalyzers, analyzer)
|
|
}
|
|
|
|
func RegisterPkgAnalyzer(analyzer PkgAnalyzer) {
|
|
pkgAnalyzers = append(pkgAnalyzers, analyzer)
|
|
}
|
|
|
|
func RegisterCommandAnalyzer(analyzer CommandAnalyzer) {
|
|
commandAnalyzers = append(commandAnalyzers, analyzer)
|
|
}
|
|
|
|
func RegisterLibraryAnalyzer(analyzer LibraryAnalyzer) {
|
|
libAnalyzers = append(libAnalyzers, analyzer)
|
|
}
|
|
|
|
func AddRequiredFilenames(filenames []string) {
|
|
additionalFiles = append(additionalFiles, filenames...)
|
|
}
|
|
|
|
func RequiredFilenames() []string {
|
|
filenames := []string{}
|
|
filenames = append(filenames, additionalFiles...)
|
|
for _, analyzer := range osAnalyzers {
|
|
filenames = append(filenames, analyzer.RequiredFiles()...)
|
|
}
|
|
for _, analyzer := range pkgAnalyzers {
|
|
filenames = append(filenames, analyzer.RequiredFiles()...)
|
|
}
|
|
for _, analyzer := range libAnalyzers {
|
|
filenames = append(filenames, analyzer.RequiredFiles()...)
|
|
}
|
|
return filenames
|
|
}
|
|
|
|
// TODO: Remove opts as they're no longer needed
|
|
func (ac Config) Analyze(ctx context.Context, imageName string, opts ...types.DockerOption) (fileMap extractor.FileMap, err error) {
|
|
r, err := ac.Extractor.SaveLocalImage(ctx, imageName)
|
|
if err != nil {
|
|
// when no docker daemon is installed or no image exists in the local machine
|
|
fileMap, err = ac.Extractor.Extract(ctx, imageName, RequiredFilenames())
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("failed to extract files: %w", err)
|
|
}
|
|
return fileMap, nil
|
|
}
|
|
|
|
fileMap, err = ac.Extractor.ExtractFromFile(ctx, r, RequiredFilenames())
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("failed to extract files from saved tar: %w", err)
|
|
}
|
|
return fileMap, nil
|
|
}
|
|
|
|
func (ac Config) AnalyzeFile(ctx context.Context, f *os.File) (fileMap extractor.FileMap, err error) {
|
|
var r io.Reader
|
|
br := bufio.NewReader(f)
|
|
if utils.IsGzip(br) {
|
|
r, err = gzip.NewReader(br)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("failed to open gzip: %w", err)
|
|
}
|
|
} else {
|
|
r = br
|
|
}
|
|
fileMap, err = ac.Extractor.ExtractFromFile(ctx, r, RequiredFilenames())
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("failed to extract files from tar: %w", err)
|
|
}
|
|
return fileMap, nil
|
|
}
|
|
|
|
func GetOS(filesMap extractor.FileMap) (OS, error) {
|
|
for _, analyzer := range osAnalyzers {
|
|
os, err := analyzer.Analyze(filesMap)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
return os, nil
|
|
}
|
|
return OS{}, ErrUnknownOS
|
|
|
|
}
|
|
|
|
func GetPackages(filesMap extractor.FileMap) ([]Package, error) {
|
|
for _, analyzer := range pkgAnalyzers {
|
|
pkgs, err := analyzer.Analyze(filesMap)
|
|
|
|
// Differentiate between a package manager not being found and another error
|
|
if err != nil && err == ErrNoPkgsDetected {
|
|
continue
|
|
} else if err != nil {
|
|
return nil, xerrors.Errorf("failed to analyze packages: %w", err)
|
|
}
|
|
return pkgs, nil
|
|
}
|
|
return nil, ErrPkgAnalysis
|
|
}
|
|
|
|
func GetPackagesFromCommands(targetOS OS, filesMap extractor.FileMap) ([]Package, error) {
|
|
for _, analyzer := range commandAnalyzers {
|
|
pkgs, err := analyzer.Analyze(targetOS, filesMap)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
return pkgs, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func CheckPackage(pkg *Package) bool {
|
|
return pkg.Name != "" && pkg.Version != ""
|
|
}
|
|
|
|
func GetLibraries(filesMap extractor.FileMap) (map[FilePath][]godeptypes.Library, error) {
|
|
results := map[FilePath][]godeptypes.Library{}
|
|
for _, analyzer := range libAnalyzers {
|
|
libMap, err := analyzer.Analyze(filesMap)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("failed to analyze libraries: %w", err)
|
|
}
|
|
|
|
for filePath, libs := range libMap {
|
|
results[filePath] = libs
|
|
}
|
|
}
|
|
return results, nil
|
|
}
|