diff --git a/cmd/cve/generate.go b/cmd/cve/generate.go index eaff03b60eb..1e7913a41c5 100644 --- a/cmd/cve/generate.go +++ b/cmd/cve/generate.go @@ -2,11 +2,13 @@ package main import ( "compress/gzip" + "context" "crypto/sha256" "encoding/hex" "flag" "fmt" "io" + "log/slog" "net/http" "os" "path/filepath" @@ -17,8 +19,6 @@ import ( "github.com/fleetdm/fleet/v4/pkg/fleethttp" "github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd" nvdsync "github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd/sync" - "github.com/go-kit/log" - "github.com/go-kit/log/level" ) const emptyData = `{ @@ -37,19 +37,20 @@ func main() { debug := flag.Bool("debug", false, "Sets debug mode") flag.Parse() - logger := log.NewJSONLogger(os.Stdout) + ctx := context.Background() + + logLevel := slog.LevelInfo if *debug { - logger = level.NewFilter(logger, level.AllowDebug()) - } else { - logger = level.NewFilter(logger, level.AllowInfo()) + logLevel = slog.LevelDebug } + logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: logLevel})) if err := os.MkdirAll(*dbDir, os.ModePerm); err != nil { panic(err) } if os.Getenv(cleanEnvVar) == "false" { - logger.Log("msg", "Downloading latest release") + logger.InfoContext(ctx, "Downloading latest release") maxRetries := 3 for i := 0; i < maxRetries; i++ { err := downloadLatestRelease(*dbDir, *debug, logger) @@ -58,11 +59,11 @@ func main() { } if i == maxRetries-1 { - logger.Log("msg", "Failed to download latest release. Continuing with full NVD Sync", "err", err) + logger.WarnContext(ctx, "Failed to download latest release. Continuing with full NVD Sync", "err", err) break } - logger.Log("msg", "Failed to download latest release. Retrying in 30 seconds", "err", err) + logger.WarnContext(ctx, "Failed to download latest release. Retrying in 30 seconds", "err", err) time.Sleep(30 * time.Second) } } @@ -74,12 +75,12 @@ func main() { // Remove Vulncheck archive if err := os.RemoveAll(filepath.Join(*dbDir, "vulncheck.zip")); err != nil { - logger.Log("msg", "Failed to remove vulncheck.zip", "err", err) + logger.WarnContext(ctx, "Failed to remove vulncheck.zip", "err", err) } // Read in every cpe file and create a corresponding metadata file // nvd data feeds start in 2002 - logger.Log("msg", "Generating metadata files ...") + logger.InfoContext(ctx, "Generating metadata files ...") const startingYear = 2002 currentYear := time.Now().Year() if currentYear < startingYear { @@ -94,7 +95,7 @@ func main() { metaName := filepath.Join(*dbDir, fileFmt(suffix, "meta", "")) // skip if file does not exist if _, err := os.Stat(fileNameRaw); os.IsNotExist(err) { - logger.Log("msg", "Skipping metadata generation for missing file", "file", fileNameRaw) + logger.InfoContext(ctx, "Skipping metadata generation for missing file", "file", fileNameRaw) continue } err := nvdsync.CompressFile(fileNameRaw, fileName) @@ -109,7 +110,7 @@ func main() { createEmptyFiles(*dbDir, "recent") } -func downloadLatestRelease(dbDir string, debug bool, logger log.Logger) error { +func downloadLatestRelease(dbDir string, debug bool, logger *slog.Logger) error { // Download the latest release err := nvd.DownloadCVEFeed(dbDir, "", debug, logger) if err != nil { diff --git a/cmd/cve/validate/main.go b/cmd/cve/validate/main.go index 9e9f2690367..d3e7db664fe 100644 --- a/cmd/cve/validate/main.go +++ b/cmd/cve/validate/main.go @@ -1,9 +1,11 @@ package main import ( + "context" "errors" "flag" "fmt" + "log/slog" "os" "path/filepath" "strings" @@ -14,8 +16,6 @@ import ( "github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd/tools/cvefeed" feednvd "github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd/tools/cvefeed/nvd" "github.com/fleetdm/fleet/v4/server/vulnerabilities/oval" - "github.com/go-kit/log" - "github.com/go-kit/log/level" ) func main() { @@ -23,21 +23,20 @@ func main() { debug := flag.Bool("debug", false, "Sets debug mode") flag.Parse() - logger := log.NewJSONLogger(os.Stdout) + logLevel := slog.LevelInfo if *debug { - logger = level.NewFilter(logger, level.AllowDebug()) - } else { - logger = level.NewFilter(logger, level.AllowInfo()) + logLevel = slog.LevelDebug } + logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: logLevel})) vulnPath := *dbDir checkNVDVulnerabilities(vulnPath, logger) checkGovalDictionaryVulnerabilities(vulnPath) } -func checkNVDVulnerabilities(vulnPath string, logger log.Logger) { +func checkNVDVulnerabilities(vulnPath string, logger *slog.Logger) { metaMap := make(map[string]fleet.CVEMeta) - if err := nvd.CVEMetaFromNVDFeedFiles(metaMap, vulnPath, logger); err != nil { + if err := nvd.CVEMetaFromNVDFeedFiles(context.Background(), metaMap, vulnPath, logger); err != nil { panic(err) } diff --git a/cmd/fleet/cron.go b/cmd/fleet/cron.go index 0bef4cfae35..c63789624ee 100644 --- a/cmd/fleet/cron.go +++ b/cmd/fleet/cron.go @@ -299,7 +299,7 @@ func checkCustomVulnerabilities( ctx, span := tracer.Start(ctx, "vuln.check_custom") defer span.End() - vulns, err := customcve.CheckCustomVulnerabilities(ctx, ds, logger, startTime) + vulns, err := customcve.CheckCustomVulnerabilities(ctx, ds, logger.SlogLogger(), startTime) if err != nil { errHandler(ctx, logger, "checking custom vulnerabilities", err) } @@ -352,7 +352,7 @@ func checkWinVulnerabilities( } start := time.Now() - r, err := msrc.Analyze(analyzeCtx, ds, o, vulnPath, collectVulns, logger) + r, err := msrc.Analyze(analyzeCtx, ds, o, vulnPath, collectVulns, logger.SlogLogger()) elapsed := time.Since(start) level.Debug(logger).Log( "msg", "msrc-analysis-done", @@ -455,7 +455,7 @@ func checkGovalDictionaryVulnerabilities( if !config.DisableDataSync { // Sync on disk goval_dictionary sqlite with current OS Versions. refreshCtx, refreshSpan := tracer.Start(ctx, "vuln.goval_dictionary.refresh") - downloaded, err := goval_dictionary.Refresh(versions, vulnPath, logger) + downloaded, err := goval_dictionary.Refresh(refreshCtx, versions, vulnPath, logger.SlogLogger()) if err != nil { errHandler(refreshCtx, logger, "updating goval_dictionary databases", err) } @@ -470,7 +470,7 @@ func checkGovalDictionaryVulnerabilities( trace.WithAttributes(attribute.Int("os_count", len(versions.OSVersions)))) for _, version := range versions.OSVersions { start := time.Now() - r, err := goval_dictionary.Analyze(analyzeCtx, ds, version, vulnPath, collectVulns, logger) + r, err := goval_dictionary.Analyze(analyzeCtx, ds, version, vulnPath, collectVulns, logger.SlogLogger()) if err != nil && errors.Is(err, goval_dictionary.ErrUnsupportedPlatform) { level.Debug(logger).Log("msg", "goval_dictionary-analysis-unsupported", "platform", version.Name) continue @@ -512,7 +512,7 @@ func checkNVDVulnerabilities( CVEFeedPrefixURL: config.CVEFeedPrefixURL, CISAKnownExploitsURL: config.CISAKnownExploitsURL, } - err := nvd.Sync(opts, logger) + err := nvd.Sync(syncCtx, opts, logger.SlogLogger()) if err != nil { errHandler(syncCtx, logger, "syncing vulnerability database", err) // don't return, continue on ... @@ -521,14 +521,14 @@ func checkNVDVulnerabilities( } loadCtx, loadSpan := tracer.Start(ctx, "vuln.nvd.load_cve_meta") - if err := nvd.LoadCVEMeta(loadCtx, logger, vulnPath, ds); err != nil { + if err := nvd.LoadCVEMeta(loadCtx, logger.SlogLogger(), vulnPath, ds); err != nil { errHandler(loadCtx, logger, "load cve meta", err) // don't return, continue on ... } loadSpan.End() cpeCtx, cpeSpan := tracer.Start(ctx, "vuln.nvd.translate_software_to_cpe") - err := nvd.TranslateSoftwareToCPE(cpeCtx, ds, vulnPath, logger) + err := nvd.TranslateSoftwareToCPE(cpeCtx, ds, vulnPath, logger.SlogLogger()) if err != nil { errHandler(cpeCtx, logger, "analyzing vulnerable software: Software->CPE", err) cpeSpan.End() @@ -537,7 +537,7 @@ func checkNVDVulnerabilities( cpeSpan.End() cveCtx, cveSpan := tracer.Start(ctx, "vuln.nvd.translate_cpe_to_cve") - vulns, err := nvd.TranslateCPEToCVE(cveCtx, ds, vulnPath, logger, collectVulns, startTime) + vulns, err := nvd.TranslateCPEToCVE(cveCtx, ds, vulnPath, logger.SlogLogger(), collectVulns, startTime) if err != nil { errHandler(cveCtx, logger, "analyzing vulnerable software: CPE->CVE", err) cveSpan.End() diff --git a/cmd/fleetctl/fleetctl/vulnerability_data_stream.go b/cmd/fleetctl/fleetctl/vulnerability_data_stream.go index 9c3e640c56d..13216ae906f 100644 --- a/cmd/fleetctl/fleetctl/vulnerability_data_stream.go +++ b/cmd/fleetctl/fleetctl/vulnerability_data_stream.go @@ -4,13 +4,13 @@ import ( "context" "errors" "fmt" + "log/slog" "os" "github.com/fleetdm/fleet/v4/server/vulnerabilities/macoffice" "github.com/fleetdm/fleet/v4/server/vulnerabilities/msrc" "github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd" "github.com/fleetdm/fleet/v4/server/vulnerabilities/oval" - klog "github.com/go-kit/log" "github.com/urfave/cli/v2" ) @@ -60,7 +60,7 @@ Downloads (if needed) the data streams that can be used by the Fleet server to p log(c, " Done\n") log(c, "[-] Downloading NVD CVE feed...") - err = nvd.DownloadCVEFeed(dir, "", false, klog.NewNopLogger()) + err = nvd.DownloadCVEFeed(dir, "", false, slog.New(slog.DiscardHandler)) if err != nil { return fmt.Errorf("Error downloading NVD CVE feed: %v", err) } diff --git a/server/vulnerabilities/customcve/matching_rules.go b/server/vulnerabilities/customcve/matching_rules.go index 8dbe9add5af..e0b3c3703f3 100644 --- a/server/vulnerabilities/customcve/matching_rules.go +++ b/server/vulnerabilities/customcve/matching_rules.go @@ -4,13 +4,12 @@ import ( "context" "errors" "fmt" + "log/slog" "strings" "time" "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd/tools/cvefeed/nvd" - "github.com/go-kit/log" - "github.com/go-kit/log/level" ) var ( @@ -154,7 +153,7 @@ func (r CVEMatchingRules) ValidateAll() error { } // CheckCustomVulnerabilities matches software against custom rules and inserts vulnerabilities -func CheckCustomVulnerabilities(ctx context.Context, ds fleet.Datastore, logger log.Logger, startTime time.Time) ([]fleet.SoftwareVulnerability, error) { +func CheckCustomVulnerabilities(ctx context.Context, ds fleet.Datastore, logger *slog.Logger, startTime time.Time) ([]fleet.SoftwareVulnerability, error) { rules := getCVEMatchingRules() if err := rules.ValidateAll(); err != nil { return nil, fmt.Errorf("invalid rules: %w", err) @@ -164,7 +163,7 @@ func CheckCustomVulnerabilities(ctx context.Context, ds fleet.Datastore, logger for i, rule := range rules { v, err := rule.match(ctx, ds) if err != nil { - level.Error(logger).Log("msg", "Error matching rule", "ruleIndex", i, "err", err) + logger.ErrorContext(ctx, "Error matching rule", "ruleIndex", i, "err", err) continue } vulns = append(vulns, v...) @@ -174,12 +173,12 @@ func CheckCustomVulnerabilities(ctx context.Context, ds fleet.Datastore, logger if err != nil { // Return early so DeleteOutOfDateVulnerabilities doesn't run. // Otherwise, without the insert refreshing updated_at, all existing vulns would look stale and be deleted. - level.Error(logger).Log("msg", "Error inserting software vulnerabilities", "err", err) + logger.ErrorContext(ctx, "Error inserting software vulnerabilities", "err", err) return nil, err } if err := ds.DeleteOutOfDateVulnerabilities(ctx, fleet.CustomSource, startTime); err != nil { - level.Error(logger).Log("msg", "Error deleting out of date vulnerabilities", "err", err) + logger.ErrorContext(ctx, "Error deleting out of date vulnerabilities", "err", err) } return newVulns, nil diff --git a/server/vulnerabilities/customcve/matching_rules_test.go b/server/vulnerabilities/customcve/matching_rules_test.go index a6078529bd7..523989125a8 100644 --- a/server/vulnerabilities/customcve/matching_rules_test.go +++ b/server/vulnerabilities/customcve/matching_rules_test.go @@ -2,6 +2,7 @@ package customcve import ( "context" + "log/slog" "sort" "testing" "time" @@ -9,7 +10,6 @@ import ( "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/mock" "github.com/fleetdm/fleet/v4/server/ptr" - "github.com/go-kit/log" "github.com/stretchr/testify/require" ) @@ -353,7 +353,7 @@ func TestCheckCustomVulnerabilities(t *testing.T) { } ctx := context.Background() - vulns, err := CheckCustomVulnerabilities(ctx, ds, log.NewNopLogger(), time.Now().UTC().Add(-time.Hour)) + vulns, err := CheckCustomVulnerabilities(ctx, ds, slog.New(slog.DiscardHandler), time.Now().UTC().Add(-time.Hour)) require.NoError(t, err) require.Len(t, vulns, 35) require.True(t, ds.DeleteOutOfDateVulnerabilitiesFuncInvoked) @@ -583,7 +583,7 @@ func TestCheckCustomVulnerabilities(t *testing.T) { } ctx := t.Context() - vulns, err := CheckCustomVulnerabilities(ctx, ds, log.NewNopLogger(), time.Now().UTC().Add(-time.Hour)) + vulns, err := CheckCustomVulnerabilities(ctx, ds, slog.New(slog.DiscardHandler), time.Now().UTC().Add(-time.Hour)) require.NoError(t, err) require.True(t, ds.DeleteOutOfDateVulnerabilitiesFuncInvoked) require.Len(t, vulns, 0) diff --git a/server/vulnerabilities/goval_dictionary/analyzer.go b/server/vulnerabilities/goval_dictionary/analyzer.go index 43606c6df21..01074bc633c 100644 --- a/server/vulnerabilities/goval_dictionary/analyzer.go +++ b/server/vulnerabilities/goval_dictionary/analyzer.go @@ -5,11 +5,11 @@ import ( "database/sql" "errors" "fmt" + "log/slog" "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/vulnerabilities/oval" "github.com/fleetdm/fleet/v4/server/vulnerabilities/utils" - kitlog "github.com/go-kit/log" ) const ( @@ -28,7 +28,7 @@ func Analyze( ver fleet.OSVersion, vulnPath string, collectVulns bool, - logger kitlog.Logger, + logger *slog.Logger, ) ([]fleet.SoftwareVulnerability, error) { platform := oval.NewPlatform(ver.Platform, ver.Name) source := fleet.GovalDictionarySource @@ -74,7 +74,7 @@ func Analyze( return nil, err } - vulnerabilities := db.Eval(software, logger) + vulnerabilities := db.Eval(ctx, software, logger) foundInBatch[hostID] = vulnerabilities } diff --git a/server/vulnerabilities/goval_dictionary/database.go b/server/vulnerabilities/goval_dictionary/database.go index a7194c9095d..8f5784ad20c 100644 --- a/server/vulnerabilities/goval_dictionary/database.go +++ b/server/vulnerabilities/goval_dictionary/database.go @@ -1,15 +1,15 @@ package goval_dictionary import ( + "context" "database/sql" "fmt" + "log/slog" "strings" "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/vulnerabilities/oval" "github.com/fleetdm/fleet/v4/server/vulnerabilities/utils" - kitlog "github.com/go-kit/log" - "github.com/go-kit/log/level" ) func NewDB(db *sql.DB, platform oval.Platform) *Database { @@ -43,7 +43,7 @@ func (db Database) Verfiy() error { // Eval evaluates the current goval_dictionary database against an OS version and a list of installed software, // returns all software vulnerabilities found. Logs on any errors so we return as many vulnerabilities as we can. -func (db Database) Eval(software []fleet.Software, logger kitlog.Logger) []fleet.SoftwareVulnerability { +func (db Database) Eval(ctx context.Context, software []fleet.Software, logger *slog.Logger) []fleet.SoftwareVulnerability { searchStmt := fmt.Sprintf("%s WHERE packages.name = ? AND packages.arch = ? ORDER BY cve_id, version", baseSearchStmt) vulnerabilities := make([]fleet.SoftwareVulnerability, 0) @@ -57,8 +57,7 @@ func (db Database) Eval(software []fleet.Software, logger kitlog.Logger) []fleet for affectedSoftwareRows.Next() { var fixedVersionWithEpochPrefix, cve string if err := affectedSoftwareRows.Scan(&fixedVersionWithEpochPrefix, &cve); err != nil { - level.Error(logger).Log( - "msg", "could not read package vulnerability result", + logger.ErrorContext(ctx, "could not read package vulnerability result", "package", swItem.Name, "arch", swItem.Arch, "platform", db.platform, @@ -91,8 +90,7 @@ func (db Database) Eval(software []fleet.Software, logger kitlog.Logger) []fleet return nil }() if err != nil { - level.Error(logger).Log( - "msg", "could not read package vulnerabilities", + logger.ErrorContext(ctx, "could not read package vulnerabilities", "package", swItem.Name, "arch", swItem.Arch, "platform", db.platform, diff --git a/server/vulnerabilities/goval_dictionary/database_test.go b/server/vulnerabilities/goval_dictionary/database_test.go index 4a18dbcbf84..a008ce81c96 100644 --- a/server/vulnerabilities/goval_dictionary/database_test.go +++ b/server/vulnerabilities/goval_dictionary/database_test.go @@ -2,11 +2,11 @@ package goval_dictionary import ( "database/sql" + "log/slog" "testing" "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/vulnerabilities/oval" - kitlog "github.com/go-kit/log" "github.com/stretchr/testify/require" ) @@ -59,26 +59,26 @@ func TestDatabase(t *testing.T) { } } db := NewDB(sqlite, oval.NewPlatform("amzn", "Amazon Linux 2.0.0")) - logger := kitlog.NewNopLogger() + logger := slog.New(slog.DiscardHandler) t.Run("Non-matching architecture", func(t *testing.T) { - require.Len(t, db.Eval([]fleet.Software{{Name: "expat", Version: "2.1.0", Release: "", Arch: "x86_64"}}, logger), 0) + require.Len(t, db.Eval(t.Context(), []fleet.Software{{Name: "expat", Version: "2.1.0", Release: "", Arch: "x86_64"}}, logger), 0) }) t.Run("Non-matching package name", func(t *testing.T) { - require.Len(t, db.Eval([]fleet.Software{{Name: "expath", Version: "2.1.0", Release: "", Arch: "aarch64"}}, logger), 0) + require.Len(t, db.Eval(t.Context(), []fleet.Software{{Name: "expath", Version: "2.1.0", Release: "", Arch: "aarch64"}}, logger), 0) }) t.Run("Fixed version", func(t *testing.T) { - require.Len(t, db.Eval([]fleet.Software{{Name: "expath", Version: "2.1.0", Release: "15.amzn2.0.3", Arch: "aarch64"}}, logger), 0) + require.Len(t, db.Eval(t.Context(), []fleet.Software{{Name: "expath", Version: "2.1.0", Release: "15.amzn2.0.3", Arch: "aarch64"}}, logger), 0) }) t.Run("Newer than fixed version", func(t *testing.T) { - require.Len(t, db.Eval([]fleet.Software{{Name: "expath", Version: "2.1.0", Release: "15.amzn2.0.5", Arch: "aarch64"}}, logger), 0) + require.Len(t, db.Eval(t.Context(), []fleet.Software{{Name: "expath", Version: "2.1.0", Release: "15.amzn2.0.5", Arch: "aarch64"}}, logger), 0) }) t.Run("Older than fixed version", func(t *testing.T) { - vulns := db.Eval([]fleet.Software{{Name: "expat", Version: "2.1.0", Release: "", Arch: "aarch64", ID: 123}}, logger) + vulns := db.Eval(t.Context(), []fleet.Software{{Name: "expat", Version: "2.1.0", Release: "", Arch: "aarch64", ID: 123}}, logger) require.Len(t, vulns, 2) require.Equal(t, "2.1.0-15.amzn2.0.3", *vulns[0].ResolvedInVersion) require.Equal(t, "2.1.0-15.amzn2.0.3", *vulns[1].ResolvedInVersion) @@ -89,14 +89,14 @@ func TestDatabase(t *testing.T) { }) t.Run("Multiple packages, fixed version", func(t *testing.T) { - require.Len(t, db.Eval([]fleet.Software{ + require.Len(t, db.Eval(t.Context(), []fleet.Software{ {Name: "expat", Version: "2.1.0", Release: "15.amzn2.1.0", Arch: "aarch64"}, {Name: "krb5-server", Version: "1.15.1", Release: "55.amzn2.2.8", Arch: "aarch64"}, }, logger), 0) }) t.Run("Multiple packages, multiple vulnerabilities", func(t *testing.T) { - vulns := db.Eval([]fleet.Software{ + vulns := db.Eval(t.Context(), []fleet.Software{ {Name: "expat", Version: "2.1.0", Release: "15.amzn2.0.2", Arch: "aarch64", ID: 234}, {Name: "krb5-server", Version: "1.15.1", Release: "55.amzn2.2.7", Arch: "aarch64", ID: 235}, }, logger) diff --git a/server/vulnerabilities/goval_dictionary/sync.go b/server/vulnerabilities/goval_dictionary/sync.go index e89fab51206..88b187a2450 100644 --- a/server/vulnerabilities/goval_dictionary/sync.go +++ b/server/vulnerabilities/goval_dictionary/sync.go @@ -1,27 +1,29 @@ package goval_dictionary import ( + "context" "fmt" + "log/slog" + "net/http" + "net/url" + "path/filepath" + "github.com/fleetdm/fleet/v4/pkg/download" "github.com/fleetdm/fleet/v4/pkg/fleethttp" "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd" "github.com/fleetdm/fleet/v4/server/vulnerabilities/oval" - kitlog "github.com/go-kit/log" - "github.com/go-kit/log/level" - "net/http" - "net/url" - "path/filepath" ) func Refresh( + ctx context.Context, versions *fleet.OSVersions, vulnPath string, - logger kitlog.Logger, + logger *slog.Logger, ) ([]oval.Platform, error) { toDownload := whatToDownload(versions) if len(toDownload) > 0 { - level.Debug(logger).Log("msg", "goval_dictionary-sync-downloading") + logger.DebugContext(ctx, "goval_dictionary-sync-downloading") err := Sync(vulnPath, toDownload) if err != nil { return nil, err diff --git a/server/vulnerabilities/msrc/analyzer.go b/server/vulnerabilities/msrc/analyzer.go index d50d087eec7..f841b25d94c 100644 --- a/server/vulnerabilities/msrc/analyzer.go +++ b/server/vulnerabilities/msrc/analyzer.go @@ -3,6 +3,7 @@ package msrc import ( "context" "fmt" + "log/slog" "strconv" "strings" "time" @@ -12,9 +13,7 @@ import ( "github.com/fleetdm/fleet/v4/server/ptr" "github.com/fleetdm/fleet/v4/server/vulnerabilities/io" msrc "github.com/fleetdm/fleet/v4/server/vulnerabilities/msrc/parsed" - utils "github.com/fleetdm/fleet/v4/server/vulnerabilities/utils" - kitlog "github.com/go-kit/log" - "github.com/go-kit/log/level" + "github.com/fleetdm/fleet/v4/server/vulnerabilities/utils" ) const ( @@ -27,7 +26,7 @@ func Analyze( os fleet.OperatingSystem, vulnPath string, collectVulns bool, - logger kitlog.Logger, + logger *slog.Logger, ) ([]fleet.OSVulnerability, error) { bulletin, err := loadBulletin(os, vulnPath) if err != nil { @@ -60,7 +59,7 @@ func Analyze( continue } // Check if the OS is vulnerable to the vulnerability by referencing the OS kernel version - isVuln, riv := isOSVulnerable(os.KernelVersion, bulletin, v, matchingPIDs, cve, logger) + isVuln, riv := isOSVulnerable(ctx, os.KernelVersion, bulletin, v, matchingPIDs, cve, logger) if isVuln { found = append(found, fleet.OSVulnerability{ OSID: os.ID, @@ -123,12 +122,13 @@ func Analyze( // If the OS is vulnerable, the function returns the version in which the vulnerability // was resolved. func isOSVulnerable( + ctx context.Context, osKernel string, b *msrc.SecurityBulletin, v msrc.Vulnerability, matchingPIDs map[string]bool, cve string, - logger kitlog.Logger, + logger *slog.Logger, ) (isVulnerable bool, resolvedInVersion string) { for KBID := range v.RemediatedBy { fix := b.VendorFixes[KBID] @@ -148,7 +148,7 @@ func isOSVulnerable( fixedBuild, feedParts, err := getBuildNumber(build) if err != nil { - level.Debug(logger).Log("msg", "invalid msrc feed version", "cve", cve, "err", err) + logger.DebugContext(ctx, "invalid msrc feed version", "cve", cve, "err", err) continue } diff --git a/server/vulnerabilities/msrc/analyzer_test.go b/server/vulnerabilities/msrc/analyzer_test.go index 0c044972d6f..c9d85be2987 100644 --- a/server/vulnerabilities/msrc/analyzer_test.go +++ b/server/vulnerabilities/msrc/analyzer_test.go @@ -3,6 +3,7 @@ package msrc import ( "context" "encoding/json" + "log/slog" "os" "path/filepath" "testing" @@ -13,7 +14,6 @@ import ( "github.com/fleetdm/fleet/v4/server/ptr" "github.com/fleetdm/fleet/v4/server/vulnerabilities/io" "github.com/fleetdm/fleet/v4/server/vulnerabilities/msrc/parsed" - "github.com/go-kit/log" "github.com/stretchr/testify/require" ) @@ -201,7 +201,7 @@ func TestIsOSVulnerable(t *testing.T) { for _, c := range tc { t.Run(c.name, func(t *testing.T) { - isVuln, resolvedIn := isOSVulnerable(c.os, &b, b.Vulnerabities[c.feed], map[string]bool{"123": true}, c.feed, log.NewNopLogger()) + isVuln, resolvedIn := isOSVulnerable(t.Context(), c.os, &b, b.Vulnerabities[c.feed], map[string]bool{"123": true}, c.feed, slog.New(slog.DiscardHandler)) require.Equal(t, c.isVulnerable, isVuln) require.Equal(t, c.resolvedIn, resolvedIn) }) @@ -422,7 +422,7 @@ func TestAnalyze(t *testing.T) { return int64(len(c.vulns)), nil } - results, err := Analyze(ctx, ds, fos, vulnPath, true, log.NewNopLogger()) + results, err := Analyze(ctx, ds, fos, vulnPath, true, slog.New(slog.DiscardHandler)) require.NoError(t, err) require.ElementsMatch(t, c.vulns, results) }) diff --git a/server/vulnerabilities/nvd/cpe.go b/server/vulnerabilities/nvd/cpe.go index d0ae3f6d739..66c0275ab28 100644 --- a/server/vulnerabilities/nvd/cpe.go +++ b/server/vulnerabilities/nvd/cpe.go @@ -5,6 +5,7 @@ import ( "database/sql" "errors" "fmt" + "log/slog" "net/url" "os" "path/filepath" @@ -21,9 +22,6 @@ import ( "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/vulnerabilities/oval" - "github.com/go-kit/log" - kitlog "github.com/go-kit/log" - "github.com/go-kit/log/level" "github.com/google/go-github/v37/github" "github.com/jmoiron/sqlx" ) @@ -240,7 +238,7 @@ var ( minioAltDate = regexp.MustCompile(`^\d{14}$`) softwareTransformers = []struct { matches func(*fleet.Software) bool - mutate func(*fleet.Software, log.Logger) + mutate func(context.Context, *fleet.Software, *slog.Logger) }{ { // JetBrains EAP version numbers aren't what are used in CPEs; this handles the translation for Mac versions. @@ -251,17 +249,17 @@ var ( return s.BundleIdentifier != "" && strings.HasPrefix(s.BundleIdentifier, "com.jetbrains.") && strings.HasPrefix(s.Version, "EAP ") && strings.Contains(s.Version, "-") }, - mutate: func(s *fleet.Software, logger log.Logger) { + mutate: func(ctx context.Context, s *fleet.Software, logger *slog.Logger) { // 243 -> 2024.3 eapMajorVersion := strings.Split(strings.Split(s.Version, "-")[1], ".")[0] yearBasedMajorVersion, err := strconv.Atoi("20" + eapMajorVersion[:2]) if err != nil { - level.Debug(logger).Log("msg", "failed to parse JetBrains EAP major version", "version", s.Version, "err", err) + logger.DebugContext(ctx, "failed to parse JetBrains EAP major version", "version", s.Version, "err", err) return } yearBasedMinorVersion, err := strconv.Atoi(eapMajorVersion[2:]) if err != nil { - level.Debug(logger).Log("msg", "failed to parse JetBrains EAP minor version", "version", s.Version, "err", err) + logger.DebugContext(ctx, "failed to parse JetBrains EAP minor version", "version", s.Version, "err", err) return } @@ -284,16 +282,16 @@ var ( matches: func(s *fleet.Software) bool { return s.Source == "programs" && strings.HasPrefix(s.Name, "Python 3.") }, - mutate: func(s *fleet.Software, logger kitlog.Logger) { + mutate: func(ctx context.Context, s *fleet.Software, logger *slog.Logger) { versionComponents := strings.Split(s.Version, ".") // Python 3 versions on Windows should always look like 3.14.102.0; if they don't we // should bail out to avoid bad indexing panics. if len(versionComponents) < 4 { - level.Debug(logger).Log("msg", "expected 4 version components", "gotCount", len(versionComponents)) + logger.DebugContext(ctx, "expected 4 version components", "gotCount", len(versionComponents)) return } if len(versionComponents[2]) < 3 { - level.Debug(logger).Log("msg", "got a patch version component with unexpected length", "gotPatchVersion", versionComponents[2]) + logger.DebugContext(ctx, "got a patch version component with unexpected length", "gotPatchVersion", versionComponents[2]) return } patchVersion := versionComponents[2][0 : len(versionComponents[2])-3] @@ -322,16 +320,16 @@ var ( matches: func(s *fleet.Software) bool { return s.Name == "Cloudflare WARP" && s.Source == "programs" }, - mutate: func(s *fleet.Software, logger log.Logger) { + mutate: func(ctx context.Context, s *fleet.Software, logger *slog.Logger) { // Perform some sanity check on the version before mutating it. parts := strings.Split(s.Version, ".") if len(parts) <= 1 { - level.Debug(logger).Log("msg", "failed to parse software version", "name", s.Name, "version", s.Version) + logger.DebugContext(ctx, "failed to parse software version", "name", s.Name, "version", s.Version) return } _, err := strconv.Atoi(parts[0]) if err != nil { - level.Debug(logger).Log("msg", "failed to parse software version", "name", s.Name, "version", s.Version, "err", err) + logger.DebugContext(ctx, "failed to parse software version", "name", s.Name, "version", s.Version, "err", err) return } // In case Cloudflare starts returning the full year. @@ -345,7 +343,7 @@ var ( matches: func(s *fleet.Software) bool { return s.Source == "apps" && (s.Name == "Microsoft Teams.app" || s.Name == "Microsoft Teams classic.app") }, - mutate: func(s *fleet.Software, logger log.Logger) { + mutate: func(ctx context.Context, s *fleet.Software, logger *slog.Logger) { if matches := macOSMSTeamsVersion.FindStringSubmatch(s.Version); len(matches) > 0 { s.Version = fmt.Sprintf("%s.%s.00.%s", matches[1], matches[2], matches[3]) } @@ -355,10 +353,10 @@ var ( matches: func(s *fleet.Software) bool { return citrixName.Match([]byte(s.Name)) || s.Name == "Citrix Workspace.app" }, - mutate: func(s *fleet.Software, logger log.Logger) { + mutate: func(ctx context.Context, s *fleet.Software, logger *slog.Logger) { parts := strings.Split(s.Version, ".") if len(parts) <= 1 { - level.Debug(logger).Log("msg", "failed to parse software version", "name", s.Name, "version", s.Version) + logger.DebugContext(ctx, "failed to parse software version", "name", s.Name, "version", s.Version) return } @@ -369,13 +367,13 @@ var ( part1, err := strconv.Atoi(parts[0]) if err != nil { - level.Debug(logger).Log("msg", "failed to parse software version", "name", s.Name, "version", s.Version, "err", err) + logger.DebugContext(ctx, "failed to parse software version", "name", s.Name, "version", s.Version, "err", err) return } part2, err := strconv.Atoi(parts[1]) if err != nil { - level.Debug(logger).Log("msg", "failed to parse software version", "name", s.Name, "version", s.Version, "err", err) + logger.DebugContext(ctx, "failed to parse software version", "name", s.Name, "version", s.Version, "err", err) return } @@ -391,7 +389,7 @@ var ( matches: func(s *fleet.Software) bool { return s.Name == "minio" && strings.Contains(s.Version, "RELEASE.") }, - mutate: func(s *fleet.Software, logger log.Logger) { + mutate: func(ctx context.Context, s *fleet.Software, logger *slog.Logger) { // trim the "RELEASE." prefix from the version s.Version = strings.TrimPrefix(s.Version, "RELEASE.") // trim any unexpected trailing characters @@ -405,10 +403,10 @@ var ( matches: func(s *fleet.Software) bool { return s.Name == "minio" && minioAltDate.MatchString(s.Version) }, - mutate: func(s *fleet.Software, logger log.Logger) { + mutate: func(ctx context.Context, s *fleet.Software, logger *slog.Logger) { timestamp, err := time.Parse("20060102150405", s.Version) if err != nil { - level.Debug(logger).Log("msg", "failed to parse software version", "name", s.Name, "version", s.Version, "err", err) + logger.DebugContext(ctx, "failed to parse software version", "name", s.Name, "version", s.Version, "err", err) return } s.Version = timestamp.Format("2006-01-02T15-04-05Z") @@ -420,7 +418,7 @@ var ( matches: func(s *fleet.Software) bool { return strings.Contains(strings.ToLower(s.Name), "powershell") }, - mutate: func(s *fleet.Software, logger log.Logger) { + mutate: func(ctx context.Context, s *fleet.Software, logger *slog.Logger) { parts := strings.Split(s.Version, ".") if len(parts) < 3 { return @@ -450,7 +448,7 @@ var ( matches: func(s *fleet.Software) bool { return s.Name == "MacVim" && s.BundleIdentifier == "org.vim.MacVim" && s.Source == "apps" }, - mutate: func(s *fleet.Software, logger log.Logger) { + mutate: func(ctx context.Context, s *fleet.Software, logger *slog.Logger) { vimToMacVimMap := map[string]string{ // r182 series "9.1.2068": "182.1", // r182.1 (prerelease) @@ -471,12 +469,12 @@ var ( } if macVimRelease, ok := vimToMacVimMap[s.Version]; ok { - level.Debug(logger).Log("msg", "converting MacVim Vim version to release number", + logger.DebugContext(ctx, "converting MacVim Vim version to release number", "original_version", s.Version, "macvim_release", macVimRelease) s.Version = macVimRelease } else { // For unknown versions, leave as-is to avoid false negatives - level.Debug(logger).Log("msg", "unknown MacVim Vim version, unable to convert to release number", + logger.DebugContext(ctx, "unknown MacVim Vim version, unable to convert to release number", "version", s.Version) } }, @@ -488,7 +486,7 @@ var ( matches: func(s *fleet.Software) bool { return s.Name == "imp" && s.Source == "homebrew_packages" }, - mutate: func(s *fleet.Software, logger log.Logger) { + mutate: func(ctx context.Context, s *fleet.Software, logger *slog.Logger) { s.Name = "integrative-modeling-platform" }, }, @@ -499,7 +497,7 @@ var ( matches: func(s *fleet.Software) bool { return s.BundleIdentifier == "com.ninxsoft.mist" && s.Source == "apps" }, - mutate: func(s *fleet.Software, logger log.Logger) { + mutate: func(ctx context.Context, s *fleet.Software, logger *slog.Logger) { s.Name = "ninxsoft-mist" }, }, @@ -510,11 +508,11 @@ var ( matches: func(s *fleet.Software) bool { return strings.HasPrefix(s.Name, "7-Zip") && s.Source == "programs" }, - mutate: func(s *fleet.Software, logger log.Logger) { + mutate: func(ctx context.Context, s *fleet.Software, logger *slog.Logger) { parts := strings.Split(s.Version, ".") switch len(parts) { case 0, 1: - level.Debug(logger).Log("msg", "unexpected 7-Zip version format", "source", "programs", "name", s.Name, "version", s.Version) + logger.DebugContext(ctx, "unexpected 7-Zip version format", "source", "programs", "name", s.Name, "version", s.Version) return case 2: return // Already in the correct format @@ -526,15 +524,15 @@ var ( } ) -func mutateSoftware(software *fleet.Software, logger log.Logger) { +func mutateSoftware(ctx context.Context, software *fleet.Software, logger *slog.Logger) { for _, transformer := range softwareTransformers { if transformer.matches(software) { defer func() { if r := recover(); r != nil { - level.Warn(logger).Log("msg", "panic during software mutation", "softwareName", software.Name, "softwareVersion", software.Version, "error", r) + logger.WarnContext(ctx, "panic during software mutation", "softwareName", software.Name, "softwareVersion", software.Version, "error", r) } }() - transformer.mutate(software, logger) + transformer.mutate(ctx, software, logger) break } } @@ -543,13 +541,13 @@ func mutateSoftware(software *fleet.Software, logger log.Logger) { // CPEFromSoftware attempts to find a matching cpe entry for the given software in the NVD CPE dictionary. `db` contains data from the NVD CPE dictionary // and is optimized for lookups, see `GenerateCPEDB`. `translations` are used to aid in cpe matching. When searching for cpes, we first check if it matches // any translations, and then lookup in the cpe database based on the title, product and vendor. -func CPEFromSoftware(logger log.Logger, db *sqlx.DB, software *fleet.Software, translations CPETranslations, reCache *regexpCache) (string, error) { +func CPEFromSoftware(ctx context.Context, logger *slog.Logger, db *sqlx.DB, software *fleet.Software, translations CPETranslations, reCache *regexpCache) (string, error) { if containsNonASCII(software.Name) { - level.Debug(logger).Log("msg", "skipping software with non-ascii characters", "software", software.Name, "version", software.Version, "source", software.Source) + logger.DebugContext(ctx, "skipping software with non-ascii characters", "software", software.Name, "version", software.Version, "source", software.Source) return "", nil } - mutateSoftware(software, logger) // tweak e.g. software versions prior to CPE matching if needed + mutateSoftware(ctx, software, logger) // tweak e.g. software versions prior to CPE matching if needed translation, match, err := translations.Translate(reCache, software) if err != nil { @@ -558,7 +556,7 @@ func CPEFromSoftware(logger log.Logger, db *sqlx.DB, software *fleet.Software, t if match { if translation.Skip { - level.Debug(logger).Log("msg", "CPE match skipped", "software", software.Name, "version", software.Version, "source", software.Source) + logger.DebugContext(ctx, "CPE match skipped", "software", software.Name, "version", software.Version, "source", software.Source) return "", nil } @@ -654,7 +652,7 @@ func CPEFromSoftware(logger log.Logger, db *sqlx.DB, software *fleet.Software, t func consumeCPEBuffer( ctx context.Context, ds fleet.Datastore, - logger kitlog.Logger, + logger *slog.Logger, batch []fleet.SoftwareCPE, ) error { var toDelete []fleet.SoftwareCPE @@ -676,7 +674,7 @@ func consumeCPEBuffer( return err } if int(upserted) != len(toUpsert) { - level.Debug(logger).Log("toUpsert", len(toUpsert), "upserted", upserted) + logger.DebugContext(ctx, "CPE upsert count mismatch", "toUpsert", len(toUpsert), "upserted", upserted) } } @@ -686,7 +684,7 @@ func consumeCPEBuffer( return err } if int(deleted) != len(toDelete) { - level.Debug(logger).Log("toDelete", len(toDelete), "deleted", deleted) + logger.DebugContext(ctx, "CPE delete count mismatch", "toDelete", len(toDelete), "deleted", deleted) } } @@ -750,7 +748,7 @@ func TranslateSoftwareToCPE( ctx context.Context, ds fleet.Datastore, vulnPath string, - logger kitlog.Logger, + logger *slog.Logger, ) error { // Skip software from sources for which we will be using OVAL or goval-dictionary for vulnerability detection. nonOvalIterator, err := ds.AllSoftwareIterator( @@ -803,7 +801,7 @@ func translateSoftwareToCPEWithIterator( ctx context.Context, ds fleet.Datastore, vulnPath string, - logger kitlog.Logger, + logger *slog.Logger, iterator fleet.SoftwareIterator, ) error { dbPath := filepath.Join(vulnPath, cpeDBFilename) @@ -817,7 +815,7 @@ func translateSoftwareToCPEWithIterator( cpeTranslationsPath := filepath.Join(vulnPath, cpeTranslationsFilename) cpeTranslations, err := loadCPETranslations(cpeTranslationsPath) if err != nil { - level.Error(logger).Log("msg", "failed to load cpe translations", "err", err) + logger.ErrorContext(ctx, "failed to load cpe translations", "err", err) } reCache := newRegexpCache() @@ -834,18 +832,16 @@ func translateSoftwareToCPEWithIterator( // Skip software without version to avoid false positives in the CPE // matching process. if software.Version == "" { - level.Debug(logger).Log( - "msg", "skipping software without version", + logger.DebugContext(ctx, "skipping software without version", "software", software.Name, "source", software.Source, ) // We want to continue here in case the software had an invalid CPE // generated by a previous version of Fleet. } else { - cpe, err = CPEFromSoftware(logger, db, software, cpeTranslations, reCache) + cpe, err = CPEFromSoftware(ctx, logger, db, software, cpeTranslations, reCache) if err != nil { - level.Error(logger).Log( - "msg", "error translating to CPE, skipping", + logger.ErrorContext(ctx, "error translating to CPE, skipping", "software", software.Name, "version", software.Version, "source", software.Source, diff --git a/server/vulnerabilities/nvd/cpe_test.go b/server/vulnerabilities/nvd/cpe_test.go index 3d9a55b8a7f..47d7fd93b53 100644 --- a/server/vulnerabilities/nvd/cpe_test.go +++ b/server/vulnerabilities/nvd/cpe_test.go @@ -3,6 +3,7 @@ package nvd import ( "compress/gzip" "context" + "log/slog" "net/http" "net/http/httptest" "os" @@ -16,8 +17,6 @@ import ( "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/mock" "github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd/tools/cpedict" - "github.com/go-kit/log" - kitlog "github.com/go-kit/log" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -39,21 +38,21 @@ func TestCPEFromSoftware(t *testing.T) { reCache := newRegexpCache() // checking a version that exists works - cpe, err := CPEFromSoftware(log.NewNopLogger(), db, &fleet.Software{Name: "Vendor Product-1.app", Version: "1.2.3", BundleIdentifier: "vendor", Source: "apps"}, nil, reCache) + cpe, err := CPEFromSoftware(t.Context(), slog.New(slog.DiscardHandler), db, &fleet.Software{Name: "Vendor Product-1.app", Version: "1.2.3", BundleIdentifier: "vendor", Source: "apps"}, nil, reCache) require.NoError(t, err) require.Equal(t, "cpe:2.3:a:vendor:product-1:1.2.3:*:*:*:*:macos:*:*", cpe) // follows many deprecations - cpe, err = CPEFromSoftware(log.NewNopLogger(), db, &fleet.Software{Name: "Vendor2 Product2.app", Version: "0.3", BundleIdentifier: "vendor2", Source: "apps"}, nil, reCache) + cpe, err = CPEFromSoftware(t.Context(), slog.New(slog.DiscardHandler), db, &fleet.Software{Name: "Vendor2 Product2.app", Version: "0.3", BundleIdentifier: "vendor2", Source: "apps"}, nil, reCache) require.NoError(t, err) require.Equal(t, "cpe:2.3:a:vendor2:product4:0.3:*:*:*:*:macos:*:*", cpe) // Does not error on Unicode Names - _, err = CPEFromSoftware(log.NewNopLogger(), db, &fleet.Software{Name: "Девушка Фонарём", Version: "1.2.3", BundleIdentifier: "vendor", Source: "apps"}, nil, reCache) + _, err = CPEFromSoftware(t.Context(), slog.New(slog.DiscardHandler), db, &fleet.Software{Name: "Девушка Фонарём", Version: "1.2.3", BundleIdentifier: "vendor", Source: "apps"}, nil, reCache) require.NoError(t, err) // Does not error on names that sanitize to empty (e.g. only special characters) - _, err = CPEFromSoftware(log.NewNopLogger(), db, &fleet.Software{Name: "[", Version: "1.2.3", BundleIdentifier: "vendor", Source: "apps"}, nil, + _, err = CPEFromSoftware(t.Context(), slog.New(slog.DiscardHandler), db, &fleet.Software{Name: "[", Version: "1.2.3", BundleIdentifier: "vendor", Source: "apps"}, nil, reCache) require.NoError(t, err) } @@ -169,7 +168,7 @@ func TestCPETranslations(t *testing.T) { for _, tc := range tt { t.Run(tc.Name, func(t *testing.T) { - cpe, err := CPEFromSoftware(log.NewNopLogger(), db, tc.Software, tc.Translations, reCache) + cpe, err := CPEFromSoftware(t.Context(), slog.New(slog.DiscardHandler), db, tc.Software, tc.Translations, reCache) require.NoError(t, err) require.Equal(t, tc.Expected, cpe) }) @@ -198,22 +197,22 @@ func TestSyncCPEDatabase(t *testing.T) { BundleIdentifier: "com.1password.1password", Source: "apps", } - cpe, err := CPEFromSoftware(log.NewNopLogger(), db, software, nil, reCache) + cpe, err := CPEFromSoftware(t.Context(), slog.New(slog.DiscardHandler), db, software, nil, reCache) require.NoError(t, err) require.Equal(t, "cpe:2.3:a:1password:1password:7.2.3:*:*:*:*:macos:*:*", cpe) - npmCPE, err := CPEFromSoftware(log.NewNopLogger(), db, &fleet.Software{Name: "Adaltas Mixme 0.4.0 for Node.js", Version: "0.4.0", Source: "npm_packages"}, nil, reCache) + npmCPE, err := CPEFromSoftware(t.Context(), slog.New(slog.DiscardHandler), db, &fleet.Software{Name: "Adaltas Mixme 0.4.0 for Node.js", Version: "0.4.0", Source: "npm_packages"}, nil, reCache) require.NoError(t, err) assert.Equal(t, "cpe:2.3:a:adaltas:mixme:0.4.0:*:*:*:*:node.js:*:*", npmCPE) - windowsCPE, err := CPEFromSoftware(log.NewNopLogger(), db, &fleet.Software{Name: "HP Storage Data Protector 8.0 for Windows 8", Version: "8.0", Source: "programs"}, nil, reCache) + windowsCPE, err := CPEFromSoftware(t.Context(), slog.New(slog.DiscardHandler), db, &fleet.Software{Name: "HP Storage Data Protector 8.0 for Windows 8", Version: "8.0", Source: "programs"}, nil, reCache) require.NoError(t, err) assert.Equal(t, "cpe:2.3:a:hp:storage_data_protector:8.0:*:*:*:*:windows:*:*", windowsCPE) // but now we truncate to make sure searching for cpe fails err = os.Truncate(dbPath, 0) require.NoError(t, err) - _, err = CPEFromSoftware(log.NewNopLogger(), db, software, nil, reCache) + _, err = CPEFromSoftware(t.Context(), slog.New(slog.DiscardHandler), db, software, nil, reCache) require.Error(t, err) // and we make the db older than the release @@ -235,7 +234,7 @@ func TestSyncCPEDatabase(t *testing.T) { require.NoError(t, err) defer db.Close() - cpe, err = CPEFromSoftware(log.NewNopLogger(), db, software, nil, reCache) + cpe, err = CPEFromSoftware(t.Context(), slog.New(slog.DiscardHandler), db, software, nil, reCache) require.NoError(t, err) require.Equal(t, "cpe:2.3:a:1password:1password:7.2.3:*:*:*:*:macos:*:*", cpe) @@ -383,7 +382,7 @@ func TestTranslateSoftwareToCPE(t *testing.T) { err = GenerateCPEDB(dbPath, items.Items) require.NoError(t, err) - err = TranslateSoftwareToCPE(context.Background(), ds, tempDir, kitlog.NewNopLogger()) + err = TranslateSoftwareToCPE(t.Context(), ds, tempDir, slog.New(slog.DiscardHandler)) require.NoError(t, err) assert.Equal(t, []string{ "cpe:2.3:a:vendor2:product4:0.3:*:*:*:*:macos:*:*", @@ -432,7 +431,7 @@ func TestTranslateSoftwareToCPEIgnoreEmptyVersion(t *testing.T) { err = GenerateCPEDB(dbPath, items.Items) require.NoError(t, err) - err = TranslateSoftwareToCPE(context.Background(), ds, tempDir, kitlog.NewNopLogger()) + err = TranslateSoftwareToCPE(t.Context(), ds, tempDir, slog.New(slog.DiscardHandler)) require.NoError(t, err) require.True(t, ds.DeleteSoftwareCPEsFuncInvoked) } @@ -1997,7 +1996,7 @@ func TestCPEFromSoftwareIntegration(t *testing.T) { for _, tt := range testCases { tt := tt - cpe, err := CPEFromSoftware(log.NewNopLogger(), db, &tt.software, cpeTranslations, reCache) + cpe, err := CPEFromSoftware(t.Context(), slog.New(slog.DiscardHandler), db, &tt.software, cpeTranslations, reCache) translation, okT, _ := cpeTranslations.Translate(reCache, &tt.software) if okT { @@ -2463,7 +2462,7 @@ func TestMutateSoftware(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - require.NotPanics(t, func() { mutateSoftware(tc.s, log.NewNopLogger()) }) + require.NotPanics(t, func() { mutateSoftware(t.Context(), tc.s, slog.New(slog.DiscardHandler)) }) require.Equal(t, tc.sanitized, tc.s) }) } diff --git a/server/vulnerabilities/nvd/cve.go b/server/vulnerabilities/nvd/cve.go index 837c1bfbb8d..8a2a3fdfdc5 100644 --- a/server/vulnerabilities/nvd/cve.go +++ b/server/vulnerabilities/nvd/cve.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io/fs" + "log/slog" "net/url" "os" "path/filepath" @@ -24,9 +25,6 @@ import ( "github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd/tools/cvefeed/nvd/schema" "github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd/tools/providers/nvd" "github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd/tools/wfn" - "github.com/go-kit/log" - kitlog "github.com/go-kit/log" - "github.com/go-kit/log/level" "github.com/google/go-github/v37/github" ) @@ -37,7 +35,7 @@ const ( // DownloadNVDCVEFeed downloads CVEs information from the NVD 2.0 API // and supplements the data with CPE information from the Vulncheck API. // This is used to download CVE information to vulnPath. -func GenerateCVEFeeds(vulnPath string, debug bool, logger log.Logger) error { +func GenerateCVEFeeds(vulnPath string, debug bool, logger *slog.Logger) error { cveSyncer, err := nvdsync.NewCVE( vulnPath, nvdsync.WithLogger(logger), @@ -58,7 +56,7 @@ func GenerateCVEFeeds(vulnPath string, debug bool, logger log.Logger) error { return nil } -func DownloadCVEFeed(vulnPath, cveFeedPrefixURL string, debug bool, logger log.Logger) error { +func DownloadCVEFeed(vulnPath, cveFeedPrefixURL string, debug bool, logger *slog.Logger) error { var err error if cveFeedPrefixURL == "" { @@ -220,7 +218,7 @@ func TranslateCPEToCVE( ctx context.Context, ds fleet.Datastore, vulnPath string, - logger kitlog.Logger, + logger *slog.Logger, collectVulns bool, startTime time.Time, ) ([]fleet.SoftwareVulnerability, error) { @@ -307,7 +305,7 @@ func TranslateCPEToCVE( newVulns, softwareInsertErr := ds.InsertSoftwareVulnerabilities(ctx, allSoftwareVulns, fleet.NVDSource) if softwareInsertErr != nil { - level.Error(logger).Log("cpe processing", "error", "err", softwareInsertErr) + logger.ErrorContext(ctx, "cpe processing error", "err", softwareInsertErr) } if !collectVulns { newVulns = nil @@ -320,7 +318,7 @@ func TranslateCPEToCVE( } osInsertErr := false if _, err := ds.InsertOSVulnerabilities(ctx, allOSVulns, fleet.NVDSource); err != nil { - level.Error(logger).Log("cpe processing", "error", "err", err) + logger.ErrorContext(ctx, "cpe processing error", "err", err) osInsertErr = true } @@ -332,12 +330,12 @@ func TranslateCPEToCVE( // Skip cleanup when the corresponding insert failed to avoid deleting data with nothing to replace it. if softwareInsertErr == nil { if err = ds.DeleteOutOfDateVulnerabilities(ctx, fleet.NVDSource, startTime); err != nil { - level.Error(logger).Log("msg", "error deleting out of date vulnerabilities", "err", err) + logger.ErrorContext(ctx, "error deleting out of date vulnerabilities", "err", err) } } if !osInsertErr { if err = ds.DeleteOutOfDateOSVulnerabilities(ctx, fleet.NVDSource, startTime); err != nil { - level.Error(logger).Log("msg", "error deleting out of date OS vulnerabilities", "err", err) + logger.ErrorContext(ctx, "error deleting out of date OS vulnerabilities", "err", err) } } @@ -407,7 +405,7 @@ func matchesExactTargetSW(softwareCPETargetSW string, targetSWs []string, config func checkCVEs( ctx context.Context, - logger kitlog.Logger, + logger *slog.Logger, cpeItems []itemWithNVDMeta, jsonFile string, knownNVDBugRules CPEMatchingRules, @@ -450,7 +448,7 @@ func checkCVEs( var softwareMu sync.Mutex var osMu sync.Mutex - logger = log.With(logger, "json_file", jsonFile) + logger = logger.With("json_file", jsonFile) for i := 0; i < runtime.NumCPU(); i++ { wg.Add(1) @@ -458,14 +456,14 @@ func checkCVEs( go func() { defer wg.Done() - logger := log.With(logger, "routine", goRoutineKey) - level.Debug(logger).Log("msg", "start") + logger := logger.With("routine", goRoutineKey) + logger.DebugContext(ctx, "start") for { select { case CPEItem, more := <-CPEItemCh: if !more { - level.Debug(logger).Log("msg", "done") + logger.DebugContext(ctx, "done") return } @@ -510,7 +508,7 @@ func checkCVEs( resolvedVersion, err := getMatchingVersionEndExcluding(ctx, matches.CVE.ID(), cpeItem, dict, logger) if err != nil { - level.Debug(logger).Log("err", err) + logger.DebugContext(ctx, "version end excluding error", "err", err) } if _, ok := CPEItem.(softwareCPEWithNVDMeta); ok { @@ -539,20 +537,20 @@ func checkCVEs( } } case <-ctx.Done(): - level.Debug(logger).Log("msg", "quitting") + logger.DebugContext(ctx, "quitting") return } } }() } - level.Debug(logger).Log("msg", "pushing cpes") + logger.DebugContext(ctx, "pushing cpes") for _, cpe := range cpeItems { CPEItemCh <- cpe } close(CPEItemCh) - level.Debug(logger).Log("msg", "cpes pushed") + logger.DebugContext(ctx, "cpes pushed") wg.Wait() return foundSoftwareVulns, foundOSVulns, nil @@ -671,7 +669,7 @@ func expandCPEAliases(cpeItem *wfn.Attributes) []*wfn.Attributes { // Returns the versionEndExcluding string for the given CVE and host software meta // data, if it exists in the NVD feed. This effectively gives us the version of the // software it needs to upgrade to in order to address the CVE. -func getMatchingVersionEndExcluding(ctx context.Context, cve string, hostSoftwareMeta *wfn.Attributes, dict cvefeed.Dictionary, logger kitlog.Logger) (string, error) { +func getMatchingVersionEndExcluding(ctx context.Context, cve string, hostSoftwareMeta *wfn.Attributes, dict cvefeed.Dictionary, logger *slog.Logger) (string, error) { vuln, ok := dict[cve].(*feednvd.Vuln) if !ok { return "", nil @@ -680,7 +678,7 @@ func getMatchingVersionEndExcluding(ctx context.Context, cve string, hostSoftwar // Schema() maps to the JSON schema of the NVD feed for a given CVE vulnSchema := vuln.Schema() if vulnSchema == nil { - level.Error(logger).Log("msg", "error getting schema for CVE", "cve", cve) + logger.ErrorContext(ctx, "error getting schema for CVE", "cve", cve) return "", nil } diff --git a/server/vulnerabilities/nvd/cve_test.go b/server/vulnerabilities/nvd/cve_test.go index 548565a8c03..fbd603380e7 100644 --- a/server/vulnerabilities/nvd/cve_test.go +++ b/server/vulnerabilities/nvd/cve_test.go @@ -3,6 +3,7 @@ package nvd import ( "context" "fmt" + "log/slog" "net/http" "net/http/httptest" "os" @@ -16,8 +17,6 @@ import ( "github.com/fleetdm/fleet/v4/server/mock" "github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd/tools/cvefeed" "github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd/tools/wfn" - "github.com/go-kit/log" - kitlog "github.com/go-kit/log" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -146,7 +145,7 @@ func TestTranslateCPEToCVE(t *testing.T) { // download the CVEs once for all sub-tests, and then disable syncing tempDir = t.TempDir() err := nettest.RunWithNetRetry(t, func() error { - return DownloadCVEFeed(tempDir, "", false, log.NewNopLogger()) + return DownloadCVEFeed(tempDir, "", false, slog.New(slog.DiscardHandler)) }) require.NoError(t, err) } else { @@ -883,7 +882,7 @@ func TestTranslateCPEToCVE(t *testing.T) { return nil } - _, err := TranslateCPEToCVE(ctx, ds, tempDir, kitlog.NewNopLogger(), false, time.Now().UTC().Add(-time.Hour)) + _, err := TranslateCPEToCVE(ctx, ds, tempDir, slog.New(slog.DiscardHandler), false, time.Now().UTC().Add(-time.Hour)) require.NoError(t, err) require.True(t, ds.DeleteOutOfDateVulnerabilitiesFuncInvoked) @@ -950,7 +949,7 @@ func TestTranslateCPEToCVE(t *testing.T) { return 0, nil } - recent, err := TranslateCPEToCVE(ctx, safeDS, tempDir, kitlog.NewNopLogger(), true, time.Now().Add(-time.Hour)) + recent, err := TranslateCPEToCVE(ctx, safeDS, tempDir, slog.New(slog.DiscardHandler), true, time.Now().Add(-time.Hour)) require.NoError(t, err) byCPE := make(map[uint]int) @@ -969,7 +968,7 @@ func TestTranslateCPEToCVE(t *testing.T) { ds.InsertSoftwareVulnerabilitiesFunc = func(ctx context.Context, vulns []fleet.SoftwareVulnerability, src fleet.VulnerabilitySource) ([]fleet.SoftwareVulnerability, error) { return nil, nil } - recent, err = TranslateCPEToCVE(ctx, safeDS, tempDir, kitlog.NewNopLogger(), true, time.Now().UTC().Add(-time.Hour)) + recent, err = TranslateCPEToCVE(ctx, safeDS, tempDir, slog.New(slog.DiscardHandler), true, time.Now().UTC().Add(-time.Hour)) require.NoError(t, err) // no recent vulnerability should be reported @@ -991,7 +990,7 @@ func TestSyncsCVEFromURL(t *testing.T) { tempDir := t.TempDir() cveFeedPrefixURL := ts.URL + "/feeds/json/cve/1.1/" - err := DownloadCVEFeed(tempDir, cveFeedPrefixURL, false, log.NewNopLogger()) + err := DownloadCVEFeed(tempDir, cveFeedPrefixURL, false, slog.New(slog.DiscardHandler)) require.Error(t, err) require.Contains(t, err.Error(), diff --git a/server/vulnerabilities/nvd/db.go b/server/vulnerabilities/nvd/db.go index 1969f9da5a1..faf5827009a 100644 --- a/server/vulnerabilities/nvd/db.go +++ b/server/vulnerabilities/nvd/db.go @@ -4,14 +4,13 @@ import ( "context" "errors" "fmt" + "log/slog" "os" "strings" "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" "github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd/tools/cpedict" "github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd/tools/wfn" - kitlog "github.com/go-kit/log" - "github.com/go-kit/log/level" "github.com/jmoiron/sqlx" _ "github.com/mattn/go-sqlite3" ) @@ -24,7 +23,7 @@ func sqliteDB(dbPath string) (*sqlx.DB, error) { return db, nil } -func sqliteDBReadOnly(ctx context.Context, dbPath string, logger kitlog.Logger) (*sqlx.DB, error) { +func sqliteDBReadOnly(ctx context.Context, dbPath string, logger *slog.Logger) (*sqlx.DB, error) { db, err := sqlx.Open("sqlite3", dbPath+"?mode=ro") if err != nil { return nil, err @@ -32,7 +31,7 @@ func sqliteDBReadOnly(ctx context.Context, dbPath string, logger kitlog.Logger) // Memory-map up to 1GB for faster reads via OS page cache. // Best-effort: don't fail if the PRAGMA isn't supported on this platform. if _, err := db.Exec("PRAGMA mmap_size = 1073741824"); err != nil { - level.Error(logger).Log("msg", "failed to set mmap_size pragma", "err", err) + logger.ErrorContext(ctx, "failed to set mmap_size pragma", "err", err) ctxerr.Handle(ctx, err) } return db, nil diff --git a/server/vulnerabilities/nvd/sync.go b/server/vulnerabilities/nvd/sync.go index 05dc9a9054f..a1808533515 100644 --- a/server/vulnerabilities/nvd/sync.go +++ b/server/vulnerabilities/nvd/sync.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "io" + "log/slog" "net/url" "os" "path/filepath" @@ -13,16 +14,13 @@ import ( "strings" "time" - "github.com/fleetdm/fleet/v4/server/contexts/license" - "github.com/fleetdm/fleet/v4/pkg/download" "github.com/fleetdm/fleet/v4/pkg/fleethttp" + "github.com/fleetdm/fleet/v4/server/contexts/license" "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/ptr" "github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd/tools/cvefeed" feednvd "github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd/tools/cvefeed/nvd" - "github.com/go-kit/log" - "github.com/go-kit/log/level" ) type SyncOptions struct { @@ -35,25 +33,25 @@ type SyncOptions struct { } // Sync downloads all the vulnerability data sources. -func Sync(opts SyncOptions, logger log.Logger) error { - level.Debug(logger).Log("msg", "syncing CPE sqlite") +func Sync(ctx context.Context, opts SyncOptions, logger *slog.Logger) error { + logger.DebugContext(ctx, "syncing CPE sqlite") start := time.Now() if err := DownloadCPEDBFromGithub(opts.VulnPath, opts.CPEDBURL); err != nil { return fmt.Errorf("sync CPE database: %w", err) } - level.Debug(logger).Log("msg", "CPE sqlite synced", "duration", time.Since(start)) + logger.DebugContext(ctx, "CPE sqlite synced", "duration", time.Since(start)) - level.Debug(logger).Log("msg", "downloading CPE translations", "url", opts.CPETranslationsURL) + logger.DebugContext(ctx, "downloading CPE translations", "url", opts.CPETranslationsURL) if err := DownloadCPETranslationsFromGithub(opts.VulnPath, opts.CPETranslationsURL); err != nil { return fmt.Errorf("sync CPE translations: %w", err) } - level.Debug(logger).Log("msg", "syncing CVEs") + logger.DebugContext(ctx, "syncing CVEs") start = time.Now() if err := DownloadCVEFeed(opts.VulnPath, opts.CVEFeedPrefixURL, opts.Debug, logger); err != nil { return fmt.Errorf("sync NVD CVE feed: %w", err) } - level.Debug(logger).Log("msg", "CVEs synced", "duration", time.Since(start)) + logger.DebugContext(ctx, "CVEs synced", "duration", time.Since(start)) if err := DownloadEPSSFeed(opts.VulnPath); err != nil { return fmt.Errorf("sync EPSS CVE feed: %w", err) @@ -190,7 +188,7 @@ func DownloadCISAKnownExploitsFeed(vulnPath string, cisaKnownExploitsURL string) return nil } -func CVEMetaFromNVDFeedFiles(metaMap map[string]fleet.CVEMeta, vulnPath string, logger log.Logger) error { +func CVEMetaFromNVDFeedFiles(ctx context.Context, metaMap map[string]fleet.CVEMeta, vulnPath string, logger *slog.Logger) error { // load cvss scores files, err := getNVDCVEFeedFiles(vulnPath) if err != nil { @@ -208,7 +206,7 @@ func CVEMetaFromNVDFeedFiles(metaMap map[string]fleet.CVEMeta, vulnPath string, for cve := range dict { vuln, ok := dict[cve].(*feednvd.Vuln) if !ok { - level.Error(logger).Log("msg", "unexpected type for Vuln interface", "cve", cve, "type", fmt.Sprintf("%T", dict[cve])) + logger.ErrorContext(ctx, "unexpected type for Vuln interface", "cve", cve, "type", fmt.Sprintf("%T", dict[cve])) continue } schema := vuln.Schema() @@ -224,7 +222,7 @@ func CVEMetaFromNVDFeedFiles(metaMap map[string]fleet.CVEMeta, vulnPath string, } if published, err := time.Parse(publishedDateFmt, schema.PublishedDate); err != nil { - level.Error(logger).Log("msg", "failed to parse published data", "cve", cve, "published_date", schema.PublishedDate, "err", err) + logger.ErrorContext(ctx, "failed to parse published data", "cve", cve, "published_date", schema.PublishedDate, "err", err) } else { meta.Published = &published } @@ -236,7 +234,7 @@ func CVEMetaFromNVDFeedFiles(metaMap map[string]fleet.CVEMeta, vulnPath string, return nil } -func CVEMetaFromEPSSFeedFiles(metaMap map[string]fleet.CVEMeta, vulnPath string, logger log.Logger) error { +func CVEMetaFromEPSSFeedFiles(metaMap map[string]fleet.CVEMeta, vulnPath string, logger *slog.Logger) error { // load epss scores path := filepath.Join(vulnPath, strings.TrimSuffix(epssFilename, ".gz")) @@ -258,7 +256,7 @@ func CVEMetaFromEPSSFeedFiles(metaMap map[string]fleet.CVEMeta, vulnPath string, return nil } -func CVEMetaFromCISAFeedFiles(metaMap map[string]fleet.CVEMeta, vulnPath string, logger log.Logger) error { +func CVEMetaFromCISAFeedFiles(metaMap map[string]fleet.CVEMeta, vulnPath string, logger *slog.Logger) error { // load known exploits path := filepath.Join(vulnPath, cisaKnownExploitsFilename) b, err := os.ReadFile(path) @@ -291,10 +289,10 @@ func CVEMetaFromCISAFeedFiles(metaMap map[string]fleet.CVEMeta, vulnPath string, return nil } -func CVEMetaFromFiles(vulnPath string, logger log.Logger) (map[string]fleet.CVEMeta, error) { +func CVEMetaFromFiles(ctx context.Context, vulnPath string, logger *slog.Logger) (map[string]fleet.CVEMeta, error) { metaMap := make(map[string]fleet.CVEMeta) - err := CVEMetaFromNVDFeedFiles(metaMap, vulnPath, logger) + err := CVEMetaFromNVDFeedFiles(ctx, metaMap, vulnPath, logger) if err != nil { return nil, fmt.Errorf("nvd meta: %w", err) } @@ -314,13 +312,13 @@ func CVEMetaFromFiles(vulnPath string, logger log.Logger) (map[string]fleet.CVEM // LoadCVEMeta loads the cvss scores, epss scores, and known exploits from the previously downloaded feeds and saves // them to the database. -func LoadCVEMeta(ctx context.Context, logger log.Logger, vulnPath string, ds fleet.Datastore) error { +func LoadCVEMeta(ctx context.Context, logger *slog.Logger, vulnPath string, ds fleet.Datastore) error { if !license.IsPremium(ctx) { - level.Info(logger).Log("msg", "skipping cve_meta parsing due to license check") + logger.InfoContext(ctx, "skipping cve_meta parsing due to license check") return nil } - metaMap, err := CVEMetaFromFiles(vulnPath, logger) + metaMap, err := CVEMetaFromFiles(ctx, vulnPath, logger) if err != nil { return err } diff --git a/server/vulnerabilities/nvd/sync/cve_syncer.go b/server/vulnerabilities/nvd/sync/cve_syncer.go index 8a95ed378a2..8be153e715c 100644 --- a/server/vulnerabilities/nvd/sync/cve_syncer.go +++ b/server/vulnerabilities/nvd/sync/cve_syncer.go @@ -12,6 +12,7 @@ import ( "fmt" "io" "io/fs" + "log/slog" "net/http" "net/url" "os" @@ -27,8 +28,6 @@ import ( "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" "github.com/fleetdm/fleet/v4/server/ptr" "github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd/tools/cvefeed/nvd/schema" - "github.com/go-kit/log" - "github.com/go-kit/log/level" "github.com/pandatix/nvdapi/common" "github.com/pandatix/nvdapi/v2" ) @@ -42,7 +41,7 @@ import ( type CVE struct { client *http.Client dbDir string - logger log.Logger + logger *slog.Logger debug bool WaitTimeForRetry time.Duration MaxTryAttempts int @@ -64,8 +63,8 @@ type CVEOption func(*CVE) // WithLogger sets the logger for a CVE syncer. // -// Default value is log.NewNopLogger(). -func WithLogger(logger log.Logger) CVEOption { +// Default value is slog.New(slog.DiscardHandler). +func WithLogger(logger *slog.Logger) CVEOption { return func(s *CVE) { s.logger = logger } @@ -90,7 +89,7 @@ func NewCVE(dbDir string, opts ...CVEOption) (*CVE, error) { s := CVE{ client: fleethttp.NewClient(), dbDir: dbDir, - logger: log.NewNopLogger(), + logger: slog.New(slog.DiscardHandler), MaxTryAttempts: maxRetryAttempts, WaitTimeForRetry: waitTimeForRetry, } @@ -111,17 +110,17 @@ func (s *CVE) Do(ctx context.Context) error { return err } if !ok { - level.Debug(s.logger).Log("msg", "initial NVD CVE sync") + s.logger.DebugContext(ctx, "initial NVD CVE sync") return s.initSync(ctx) } - level.Debug(s.logger).Log("msg", "NVD CVE update") + s.logger.DebugContext(ctx, "NVD CVE update") return s.update(ctx) } // initSync performs the initial synchronization (full download) of all CVEs. func (s *CVE) initSync(ctx context.Context) error { // Remove any legacy feeds from previous versions of Fleet. - if err := s.removeLegacyFeeds(); err != nil { + if err := s.removeLegacyFeeds(ctx); err != nil { return err } @@ -140,7 +139,7 @@ func (s *CVE) initSync(ctx context.Context) error { } // removeLegacyFeeds removes all the legacy feed files downloaded by previous versions of Fleet. -func (s *CVE) removeLegacyFeeds() error { +func (s *CVE) removeLegacyFeeds(ctx context.Context) error { // Using * to remove new unfinished syncs (uncompressed) jsonGzs, err := filepath.Glob(filepath.Join(s.dbDir, "nvdcve-1.1-*.json*")) if err != nil { @@ -151,7 +150,7 @@ func (s *CVE) removeLegacyFeeds() error { return err } for _, path := range append(jsonGzs, metas...) { - level.Debug(s.logger).Log("msg", "removing legacy feed file", "path", path) + s.logger.DebugContext(ctx, "removing legacy feed file", "path", path) if err := os.Remove(path); err != nil { return err } @@ -202,7 +201,7 @@ func (s *CVE) update(ctx context.Context) error { return nil } -func (s *CVE) updateYearFile(year int, cves []nvdapi.CVEItem) error { +func (s *CVE) updateYearFile(ctx context.Context, year int, cves []nvdapi.CVEItem) error { // The NVD legacy feed files start at year 2002. // This is assumed by the github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd/tools package. if year < 2002 { @@ -215,7 +214,7 @@ func (s *CVE) updateYearFile(year int, cves []nvdapi.CVEItem) error { if err != nil { return err } - level.Debug(s.logger).Log("msg", "read cves", "year", year, "duration", time.Since(readStart)) + s.logger.DebugContext(ctx, "read cves", "year", year, "duration", time.Since(readStart)) // Convert new API 2.0 format to legacy feed format and create map of new CVE information. newLegacyCVEs := make(map[string]*schema.NVDCVEFeedJSON10DefCVEItem) @@ -223,7 +222,7 @@ func (s *CVE) updateYearFile(year int, cves []nvdapi.CVEItem) error { if cve.CVE.VulnStatus != nil && *cve.CVE.VulnStatus == "Rejected" { continue } - legacyCVE := convertAPI20CVEToLegacy(cve.CVE, s.logger) + legacyCVE := convertAPI20CVEToLegacy(ctx, cve.CVE, s.logger) newLegacyCVEs[legacyCVE.CVE.CVEDataMeta.ID] = legacyCVE } @@ -238,7 +237,7 @@ func (s *CVE) updateYearFile(year int, cves []nvdapi.CVEItem) error { delete(newLegacyCVEs, storedCVE.CVE.CVEDataMeta.ID) } } - level.Debug(s.logger).Log("msg", "updated cves", "year", year, "duration", time.Since(updateStart)) + s.logger.DebugContext(ctx, "updated cves", "year", year, "duration", time.Since(updateStart)) // Add any new CVEs (e.g. a new vulnerability has been found since last time so a new CVE number was reported). // @@ -253,14 +252,14 @@ func (s *CVE) updateYearFile(year int, cves []nvdapi.CVEItem) error { if err := storeCVEsInLegacyFormat(s.dbDir, year, storedCVEFeed); err != nil { return err } - level.Debug(s.logger).Log("msg", "stored cves", "year", year, "duration", time.Since(storeStart)) + s.logger.DebugContext(ctx, "stored cves", "year", year, "duration", time.Since(storeStart)) return nil } var cachedCVEFeeds = map[int]*schema.NVDCVEFeedJSON10{} -func (s *CVE) updateVulnCheckYearFile(year int, cves []VulnCheckCVE, modCount, addCount *int) error { +func (s *CVE) updateVulnCheckYearFile(ctx context.Context, year int, cves []VulnCheckCVE, modCount, addCount *int) error { // The NVD legacy feed files start at year 2002. // This is assumed by the facebookincubator/nvdtools package. if year < 2002 { @@ -286,7 +285,7 @@ func (s *CVE) updateVulnCheckYearFile(year int, cves []VulnCheckCVE, modCount, a if cve.CVE.VulnStatus != nil && *cve.CVE.VulnStatus == "Rejected" { continue } - legacyCVE := convertAPI20CVEToLegacy(cve.CVE, s.logger) + legacyCVE := convertAPI20CVEToLegacy(ctx, cve.CVE, s.logger) updateWithVulnCheckConfigurations(legacyCVE, cve.VcConfigurations) newLegacyCVEs[legacyCVE.CVE.CVEDataMeta.ID] = legacyCVE } @@ -313,12 +312,12 @@ func (s *CVE) updateVulnCheckYearFile(year int, cves []VulnCheckCVE, modCount, a } } *modCount += counter - level.Debug(s.logger).Log("msg", "updating vulncheck cves", "year", year, "count", counter) + s.logger.DebugContext(ctx, "updating vulncheck cves", "year", year, "count", counter) // Add any new CVEs (e.g. a new vulnerability has been found since last time so a new CVE number was reported). // // Any leftover items from the previous loop in newLegacyCVEs are new CVEs. - level.Debug(s.logger).Log("msg", "adding new vulncheck cves", "year", year, "count", len(newLegacyCVEs), "duration", time.Since(updateStart)) + s.logger.DebugContext(ctx, "adding new vulncheck cves", "year", year, "count", len(newLegacyCVEs), "duration", time.Since(updateStart)) *addCount += len(newLegacyCVEs) for _, cve := range newLegacyCVEs { storedCVEFeed.CVEItems = append(storedCVEFeed.CVEItems, cve) @@ -424,7 +423,7 @@ func (s *CVE) sync(ctx context.Context, lastModStartDate *string) (newLastModSta if retryAttempts > maxRetryAttempts { return "", err } - s.logger.Log("msg", "NVD request returned error", "err", err, "retry-in", waitTimeForRetry) + s.logger.WarnContext(ctx, "NVD request returned error", "err", err, "retry-in", waitTimeForRetry) retryAttempts++ select { case <-ctx.Done(): @@ -473,11 +472,11 @@ func (s *CVE) sync(ctx context.Context, lastModStartDate *string) (newLastModSta } } start := time.Now() - if err := s.updateYearFile(yearWithMostVulns, cvesByYear[yearWithMostVulns]); err != nil { + if err := s.updateYearFile(ctx, yearWithMostVulns, cvesByYear[yearWithMostVulns]); err != nil { return "", err } updateDuration = time.Since(start) - level.Debug(s.logger).Log("msg", "updated file", "year", yearWithMostVulns, "duration", updateDuration, "vulns", maxVulnsInYear) + s.logger.DebugContext(ctx, "updated file", "year", yearWithMostVulns, "duration", updateDuration, "vulns", maxVulnsInYear) vulnerabilitiesReceived -= maxVulnsInYear delete(cvesByYear, yearWithMostVulns) @@ -494,10 +493,10 @@ func (s *CVE) sync(ctx context.Context, lastModStartDate *string) (newLastModSta for year, cvesInYear := range cvesByYear { start := time.Now() - if err := s.updateYearFile(year, cvesInYear); err != nil { + if err := s.updateYearFile(ctx, year, cvesInYear); err != nil { return "", err } - level.Debug(s.logger).Log("msg", "updated file", "year", year, "duration", time.Since(start), "vulns", len(cvesInYear)) + s.logger.DebugContext(ctx, "updated file", "year", year, "duration", time.Since(start), "vulns", len(cvesInYear)) } return newLastModStartDate, nil @@ -557,7 +556,7 @@ func (s *CVE) DoVulnCheck(ctx context.Context) error { return ctxerr.Wrap(ctx, err, "error downloading archive") } - err = s.processVulnCheckFile(vulnCheckArchive) + err = s.processVulnCheckFile(ctx, vulnCheckArchive) if err != nil { return fmt.Errorf("error processing VulnCheck file: %w", err) } @@ -592,7 +591,7 @@ func (s *CVE) fetchVulnCheckDownloadURL(ctx context.Context, baseURL string) (st if resp != nil { resp.Body.Close() } - s.logger.Log("msg", "VulnCheck API request failed", "attempt", attempt, "error", err) + s.logger.WarnContext(ctx, "VulnCheck API request failed", "attempt", attempt, "error", err) if attempt == s.MaxTryAttempts { return "", ctxerr.Wrap(ctx, err, "max retry attempts reached") } @@ -605,7 +604,7 @@ func (s *CVE) fetchVulnCheckDownloadURL(ctx context.Context, baseURL string) (st } resp.Body.Close() // Close the body if we are going to retry or fail - s.logger.Log("msg", "VulnCheck API request failed", "attempt", attempt, "status", resp.StatusCode, "retry-in", s.WaitTimeForRetry) + s.logger.WarnContext(ctx, "VulnCheck API request failed", "attempt", attempt, "status", resp.StatusCode, "retry-in", s.WaitTimeForRetry) if attempt == s.MaxTryAttempts { return "", ctxerr.New(ctx, "max retry attempts reached") } @@ -674,7 +673,7 @@ func (s *CVE) downloadVulnCheckArchive(ctx context.Context, downloadURL, outFile return nil } -func (s *CVE) processVulnCheckFile(fileName string) error { +func (s *CVE) processVulnCheckFile(ctx context.Context, fileName string) error { sanitizedPath, err := sanitizeArchivePath(s.dbDir, fileName) if err != nil { return fmt.Errorf("error sanitizing archive path: %w", err) @@ -738,10 +737,10 @@ func (s *CVE) processVulnCheckFile(fileName string) error { cvesByYear[year] = append(cvesByYear[year], cve.Item) } - level.Debug(s.logger).Log("msg", "read vulncheck file", "file", file.Name) + s.logger.DebugContext(ctx, "read vulncheck file", "file", file.Name) for year, cvesInYear := range cvesByYear { - if err := s.updateVulnCheckYearFile(year, cvesInYear, &modCount, &addCount); err != nil { + if err := s.updateVulnCheckYearFile(ctx, year, cvesInYear, &modCount, &addCount); err != nil { return err } } @@ -758,7 +757,7 @@ func (s *CVE) processVulnCheckFile(fileName string) error { } } - level.Debug(s.logger).Log("total updated", modCount, "total added", addCount, "store duration", time.Since(storeStart)) + s.logger.DebugContext(ctx, "vulncheck sync complete", "total_updated", modCount, "total_added", addCount, "store_duration", time.Since(storeStart)) return nil } @@ -848,8 +847,8 @@ func derefPtr[T any](p *T) T { } // convertAPI20CVEToLegacy performs the conversion of a CVE in API 2.0 format to the legacy feed format. -func convertAPI20CVEToLegacy(cve nvdapi.CVE, logger log.Logger) *schema.NVDCVEFeedJSON10DefCVEItem { - logger = log.With(logger, "cve", cve.ID) +func convertAPI20CVEToLegacy(ctx context.Context, cve nvdapi.CVE, logger *slog.Logger) *schema.NVDCVEFeedJSON10DefCVEItem { + logger = logger.With("cve", cve.ID) descriptions := make([]*schema.CVEJSON40LangString, 0, len(cve.Descriptions)) for _, description := range cve.Descriptions { @@ -865,7 +864,7 @@ func convertAPI20CVEToLegacy(cve nvdapi.CVE, logger log.Logger) *schema.NVDCVEFe continue // non-English descriptions with unknown language tags are ignored and warned. default: - level.Warn(logger).Log("msg", "Unknown CVE description language tag", "lang", description.Lang) + logger.WarnContext(ctx, "Unknown CVE description language tag", "lang", description.Lang) continue } descriptions = append(descriptions, &schema.CVEJSON40LangString{ @@ -1127,11 +1126,11 @@ func convertAPI20CVEToLegacy(cve nvdapi.CVE, logger log.Logger) *schema.NVDCVEFe lastModified, err := convertAPI20TimeToLegacy(cve.LastModified) if err != nil { - logger.Log("msg", "failed to parse lastModified time", "err", err) + logger.WarnContext(ctx, "failed to parse lastModified time", "err", err) } publishedDate, err := convertAPI20TimeToLegacy(cve.Published) if err != nil { - logger.Log("msg", "failed to parse published time", "err", err) + logger.WarnContext(ctx, "failed to parse published time", "err", err) } return &schema.NVDCVEFeedJSON10DefCVEItem{ diff --git a/server/vulnerabilities/nvd/sync/cve_syncer_test.go b/server/vulnerabilities/nvd/sync/cve_syncer_test.go index b44d9024e74..fa6ffb94e87 100644 --- a/server/vulnerabilities/nvd/sync/cve_syncer_test.go +++ b/server/vulnerabilities/nvd/sync/cve_syncer_test.go @@ -8,6 +8,7 @@ import ( "encoding/json" "fmt" "io" + "log/slog" "net/http" "net/http/httptest" "os" @@ -19,7 +20,6 @@ import ( "time" "github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd/tools/cvefeed/nvd/schema" - "github.com/go-kit/log" "github.com/google/go-cmp/cmp" "github.com/pandatix/nvdapi/v2" "github.com/stretchr/testify/require" @@ -58,7 +58,7 @@ func TestStoreCVEsLegacyFormat(t *testing.T) { matched = 0 ) for _, api20Vuln := range api20CVEs { - convertedLegacyVuln := convertAPI20CVEToLegacy(api20Vuln.CVE, log.NewNopLogger()) + convertedLegacyVuln := convertAPI20CVEToLegacy(t.Context(), api20Vuln.CVE, slog.New(slog.DiscardHandler)) legacyVuln, ok := legacyVulns[*api20Vuln.CVE.ID] if !ok { vulnsNotFoundInLegacy = append(vulnsNotFoundInLegacy, *api20Vuln.CVE.ID) @@ -216,7 +216,7 @@ func TestEnhanceNVDwithVulncheck(t *testing.T) { syncer, err := NewCVE(testDataPath) require.NoError(t, err) - err = syncer.processVulnCheckFile("vulncheck.zip") + err = syncer.processVulnCheckFile(t.Context(), "vulncheck.zip") require.NoError(t, err) // compare the enhanced data with the expected data diff --git a/server/vulnerabilities/nvd/sync_test.go b/server/vulnerabilities/nvd/sync_test.go index a1e165f1199..06ee25a67ce 100644 --- a/server/vulnerabilities/nvd/sync_test.go +++ b/server/vulnerabilities/nvd/sync_test.go @@ -2,16 +2,15 @@ package nvd import ( "context" + "log/slog" "path/filepath" "strings" "testing" - "github.com/fleetdm/fleet/v4/server/contexts/license" - "github.com/fleetdm/fleet/v4/pkg/nettest" + "github.com/fleetdm/fleet/v4/server/contexts/license" "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/mock" - "github.com/go-kit/log" "github.com/stretchr/testify/require" "github.com/tj/assert" ) @@ -58,7 +57,7 @@ func TestLoadCVEMeta(t *testing.T) { return nil } - logger := log.NewNopLogger() + logger := slog.New(slog.DiscardHandler) err := LoadCVEMeta(license.NewContext(context.Background(), &fleet.LicenseInfo{ Tier: "premium", }), logger, "../testdata", ds) diff --git a/tools/nvd/nvdvuln/nvdvuln.go b/tools/nvd/nvdvuln/nvdvuln.go index 0b62316d13d..361d5ba7f0f 100644 --- a/tools/nvd/nvdvuln/nvdvuln.go +++ b/tools/nvd/nvdvuln/nvdvuln.go @@ -4,6 +4,7 @@ import ( "context" "flag" "fmt" + "log/slog" "os" "path/filepath" "sort" @@ -14,8 +15,6 @@ import ( "github.com/fleetdm/fleet/v4/server/mock" "github.com/fleetdm/fleet/v4/server/service" "github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd" - "github.com/go-kit/log" - "github.com/go-kit/log/level" "github.com/google/go-cmp/cmp" "github.com/shirou/gopsutil/v3/process" ) @@ -108,17 +107,17 @@ func main() { }() } - logger := log.NewJSONLogger(os.Stdout) - logger = log.With(logger, "ts", log.DefaultTimestampUTC) + logLevel := slog.LevelInfo if *debug { - logger = level.NewFilter(logger, level.AllowDebug()) - } else { - logger = level.NewFilter(logger, level.AllowInfo()) + logLevel = slog.LevelDebug } + logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: logLevel})) + + ctx := context.Background() if *sync { printf("Syncing into %s...\n", *dbDir) - if err := vulnDBSync(*dbDir, *debug, logger); err != nil { + if err := vulnDBSync(ctx, *dbDir, *debug, logger); err != nil { panic(err) } if !singleSoftwareSet && !softwareFromURLSet { @@ -126,8 +125,6 @@ func main() { } } - ctx := context.Background() - var software []fleet.Software if singleSoftwareSet { software = []fleet.Software{ @@ -296,12 +293,12 @@ func (s *softwareIterator) Close() error { return nil } -func vulnDBSync(vulnDBDir string, debug bool, logger log.Logger) error { +func vulnDBSync(ctx context.Context, vulnDBDir string, debug bool, logger *slog.Logger) error { opts := nvd.SyncOptions{ VulnPath: vulnDBDir, Debug: debug, } - err := nvd.Sync(opts, logger) + err := nvd.Sync(ctx, opts, logger) if err != nil { return err }