internal/vulnerability/scanner.go (177 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package vulnerability import ( "context" "encoding/json" "fmt" "io" "os" "time" db_types "github.com/aquasecurity/trivy-db/pkg/types" fanal_types "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/flag" trivy_types "github.com/aquasecurity/trivy/pkg/types" cb_config "github.com/elastic/cloudbeat/internal/config" "github.com/elastic/cloudbeat/internal/infra/clog" "github.com/elastic/cloudbeat/internal/resources/providers/awslib/ec2" ) type VulnerabilityScanner struct { log *clog.Logger ch chan []Result runner runnerInterface cfg *cb_config.Config seq time.Time manager *SnapshotManager } const shutdownGracePeriod = 30 * time.Second type Result struct { reportResult trivy_types.Result vulnerability trivy_types.DetectedVulnerability snapshot ec2.EBSSnapshot seq time.Time } type runnerInterface interface { ScanVM(ctx context.Context, opts flag.Options) (trivy_types.Report, error) Filter(ctx context.Context, opts flag.Options, report trivy_types.Report) (trivy_types.Report, error) Report(ctx context.Context, opts flag.Options, report trivy_types.Report) error Close(ctx context.Context) error } // TODO: Replace sequence with more generic approach func NewVulnerabilityScanner(log *clog.Logger, runner runnerInterface, manager *SnapshotManager, c *cb_config.Config, seq time.Time) (VulnerabilityScanner, error) { log.Debug("VulnerabilityScanner: New") ch := make(chan []Result) return VulnerabilityScanner{ log: log, ch: ch, runner: runner, cfg: c, // TODO: Replace sequence with more generic approach seq: seq, manager: manager, }, nil } func (f VulnerabilityScanner) ScanSnapshot(ctx context.Context, snapCh chan ec2.EBSSnapshot) { f.log.Info("Starting NewVulnerabilityScanner.ScanSnapshot") defer close(f.ch) for { select { case <-ctx.Done(): f.log.Info("NewVulnerabilityScanner.ScanSnapshot context canceled") return case snap, ok := <-snapCh: if !ok { f.log.Info("NewVulnerabilityScanner.ScanSnapshot channel is closed") return } f.scan(ctx, snap) f.manager.DeleteSnapshot(ctx, snap) } } } func (f VulnerabilityScanner) scan(ctx context.Context, snap ec2.EBSSnapshot) { f.log.Infof("Starting VulnerabilityScanner.scan, %s", snap.SnapshotId) defer func() { if r := recover(); r != nil { f.log.Errorf("vulnerability scanner recovered from panic: %v", r) } }() o, err := os.CreateTemp("", "") if err != nil { f.log.Error("VulnerabilityScanner.scan.TempFile error: ", err) return } defer func(name string) { err = os.Remove(name) if err != nil { f.log.Warnf("Failed to remove temporary file %s: %v", name, err) } }(o.Name()) opts := flag.Options{ GlobalOptions: flag.GlobalOptions{ // TODO: Make configurable Timeout: 1 * time.Hour, Quiet: false, Debug: true, }, PackageOptions: flag.PackageOptions{ PkgTypes: []string{trivy_types.PkgTypeOS, trivy_types.PkgTypeLibrary}, PkgRelationships: fanal_types.Relationships, }, ScanOptions: flag.ScanOptions{ Target: fmt.Sprint("ebs:", snap.SnapshotId), Scanners: []trivy_types.Scanner{trivy_types.VulnerabilityScanner}, RekorURL: "https://rekor.sigstore.dev", }, AWSOptions: flag.AWSOptions{ Region: snap.Region, }, DBOptions: flag.DBOptions{ SkipDBUpdate: true, SkipJavaDBUpdate: true, }, ReportOptions: flag.ReportOptions{ Output: o.Name(), Format: "json", Severities: []db_types.Severity{0, 1, 2, 3, 4}, }, } now := time.Now() report, err := f.runner.ScanVM(ctx, opts) f.log.Infof( "VulnerabilityScanner.scan.ScanVM took %s to scan %s, volume size: %d, isEncrypted: %t, instanceId: %s", time.Since(now), snap.SnapshotId, snap.VolumeSize, snap.IsEncrypted, *snap.Instance.InstanceId, ) if err != nil { f.log.Errorf("VulnerabilityScanner.scan.ScanVM, snapshotId: %s, instanceId: %s, error: %v", snap.SnapshotId, *snap.Instance.InstanceId, err) return } f.log.Info("VulnerabilityScanner.scan.Filter") report, err = f.runner.Filter(ctx, opts, report) if err != nil { f.log.Error("VulnerabilityScanner.scan.Filter error: ", err) return } f.log.Info("VulnerabilityScanner.scan.Report") err = f.runner.Report(ctx, opts, report) if err != nil { f.log.Error("VulnerabilityScanner.scan.Report error: ", err) return } f.log.Info("VulnerabilityScanner.scan.jsonFile") jsonFile, err := os.Open(o.Name()) if err != nil { f.log.Error("VulnerabilityScanner.scan.jsonFile error: ", err) return } defer jsonFile.Close() f.log.Info("VulnerabilityScanner.scan.ReadAll") byteValue, _ := io.ReadAll(jsonFile) var unmarshalledReport trivy_types.Report err = json.Unmarshal(byteValue, &unmarshalledReport) if err != nil { f.log.Error("VulnerabilityScanner.scan.Unmarshal error: ", err) return } results := []Result{} for _, result := range unmarshalledReport.Results { for _, vul := range result.Vulnerabilities { // TODO: Replace sequence with more generic approach result := Result{ reportResult: result, vulnerability: vul, snapshot: snap, seq: f.seq, } results = append(results, result) } } select { case <-ctx.Done(): f.log.Info("VulnerabilityScanner.scan context canceled while sending vulnerabilities") return case f.ch <- results: } f.log.Info("VulnerabilityScanner.scan.DONE") } func (f VulnerabilityScanner) GetChan() chan []Result { return f.ch }