package scanner
import (
"context"
"errors"
"fmt"
"strings"
"buf.build/gen/go/safedep/api/grpc/go/safedep/services/malysis/v1/malysisv1grpc"
malysisv1pb "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/malysis/v1"
packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1"
malysisv1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/services/malysis/v1"
"github.com/safedep/dry/adapters"
"github.com/safedep/vet/pkg/common/logger"
"github.com/safedep/vet/pkg/common/utils"
"github.com/safedep/vet/pkg/models"
"google.golang.org/grpc"
)
type malysisMalwareAnalysisQueryEnricher struct {
cc *grpc.ClientConn
client malysisv1grpc.MalwareAnalysisServiceClient
config MalysisMalwareEnricherConfig
gha *adapters.GithubClient
}
var _ PackageMetaEnricher = &malysisMalwareAnalysisQueryEnricher{}
// NewMalysisMalwareAnalysisQueryEnricher creates a new malware analysis query enricher.
// We are re-using the config from the malware enricher because this enricher is a subset
// of the malware enricher.
func NewMalysisMalwareAnalysisQueryEnricher(cc *grpc.ClientConn,
gha *adapters.GithubClient,
config MalysisMalwareEnricherConfig,
) (*malysisMalwareAnalysisQueryEnricher, error) {
if cc == nil {
return nil, errors.New("grpc client connection is required")
}
if gha == nil {
return nil, errors.New("github client is required")
}
client := malysisv1grpc.NewMalwareAnalysisServiceClient(cc)
return &malysisMalwareAnalysisQueryEnricher{
cc: cc,
client: client,
config: config,
gha: gha,
}, nil
}
func (e *malysisMalwareAnalysisQueryEnricher) Name() string {
return "malysis-malware-analysis-query-enricher"
}
func (e *malysisMalwareAnalysisQueryEnricher) Enrich(pkg *models.Package, cb PackageDependencyCallbackFn) error {
logger.Infof("[Malware Analysis] Enriching package with malware analysis query: %s/%s/%s",
pkg.Manifest.Ecosystem, pkg.PackageDetails.Name, pkg.PackageDetails.Version)
req := malysisv1.QueryPackageAnalysisRequest{
Target: &malysisv1pb.PackageAnalysisTarget{
PackageVersion: &packagev1.PackageVersion{
Package: &packagev1.Package{
Ecosystem: pkg.GetControlTowerSpecEcosystem(),
Name: pkg.GetName(),
},
Version: pkg.GetVersion(),
},
},
}
ctx, cancelFn := context.WithTimeout(context.Background(), e.config.GrpcOperationTimeout)
defer cancelFn()
if req.GetTarget().GetPackageVersion().GetPackage().GetEcosystem() == packagev1.Ecosystem_ECOSYSTEM_GITHUB_ACTIONS {
logger.Infof("[Malware Analysis] Resolving commit hash for GitHub Actions package: %s/%s",
pkg.GetName(), pkg.GetVersion())
parts := strings.Split(pkg.GetName(), "/")
if len(parts) != 2 {
return fmt.Errorf("invalid package name: %s for GitHub Actions - should be in the format <owner>/<repo>", pkg.GetName())
}
commitHash, err := utils.ResolveGitHubRepositoryCommitSHA(ctx, e.gha, parts[0], parts[1], pkg.GetVersion())
if err != nil {
return err
}
logger.Infof("[Malware Analysis] Resolved commit hash for GitHub Actions package: %s/%s@%s",
parts[0], parts[1], commitHash)
req.GetTarget().GetPackageVersion().Version = commitHash
}
res, err := e.client.QueryPackageAnalysis(ctx, &req)
if err != nil {
return fmt.Errorf("failed to query package analysis: %w", err)
}
logger.Infof("[Malware Analysis] Applying package analysis results with analysisId: %s to package: %s/%s/%s",
res.GetAnalysisId(), pkg.GetControlTowerSpecEcosystem(), pkg.GetName(), pkg.GetVersion())
pkg.SetMalwareAnalysisResult(&models.MalwareAnalysisResult{
AnalysisId: res.GetAnalysisId(),
Report: res.GetReport(),
VerificationRecord: res.GetVerificationRecord(),
})
return nil
}
func (e *malysisMalwareAnalysisQueryEnricher) Wait() error {
return nil
}