fleet/server/vulnerabilities/cve_test.go
2022-06-09 10:36:37 -03:00

166 lines
5.1 KiB
Go

package vulnerabilities
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"os"
"runtime"
"strings"
"sync"
"testing"
"github.com/fleetdm/fleet/v4/pkg/nettest"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/mock"
kitlog "github.com/go-kit/kit/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var cvetests = []struct {
cpe, cve string
}{
{"cpe:2.3:a:1password:1password:3.9.9:*:*:*:*:macos:*:*", "CVE-2012-6369"},
{"cpe:2.3:a:1password:1password:3.9.9:*:*:*:*:*:*:*", "CVE-2012-6369"},
}
func printMemUsage() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
// For info on each, see: https://golang.org/pkg/runtime/#MemStats
fmt.Printf("Alloc = %v MiB", bToMb(m.Alloc))
fmt.Printf("\tTotalAlloc = %v MiB", bToMb(m.TotalAlloc))
fmt.Printf("\tSys = %v MiB", bToMb(m.Sys))
fmt.Printf("\tNumGC = %v\n", m.NumGC)
}
func bToMb(b uint64) uint64 {
return b / 1024 / 1024
}
type threadSafeDSMock struct {
mu sync.Mutex
*mock.Store
}
func (d *threadSafeDSMock) ListSoftwareCPEs(ctx context.Context, excludedPlatforms []string) ([]fleet.SoftwareCPE, error) {
d.mu.Lock()
defer d.mu.Unlock()
return d.Store.ListSoftwareCPEs(ctx, excludedPlatforms)
}
func (d *threadSafeDSMock) InsertVulnerabilities(ctx context.Context, vulns []fleet.SoftwareVulnerability, src fleet.VulnerabilitySource) (int64, error) {
d.mu.Lock()
defer d.mu.Unlock()
return d.Store.InsertVulnerabilities(ctx, vulns, src)
}
func TestTranslateCPEToCVE(t *testing.T) {
nettest.Run(t)
tempDir := t.TempDir()
ds := new(mock.Store)
ctx := context.Background()
// download the CVEs once for all sub-tests, and then disable syncing
err := nettest.RunWithNetRetry(t, func() error {
return DownloadNVDCVEFeed(tempDir, "")
})
require.NoError(t, err)
for _, tt := range cvetests {
t.Run(tt.cpe, func(t *testing.T) {
ds.ListSoftwareCPEsFunc = func(ctx context.Context, excludedPlatforms []string) ([]fleet.SoftwareCPE, error) {
return []fleet.SoftwareCPE{
{CPE: tt.cpe},
}, nil
}
cveLock := &sync.Mutex{}
var cvesFound []string
ds.InsertVulnerabilitiesFunc = func(ctx context.Context, vulns []fleet.SoftwareVulnerability, src fleet.VulnerabilitySource) (int64, error) {
cveLock.Lock()
defer cveLock.Unlock()
for _, v := range vulns {
cvesFound = append(cvesFound, v.CVE)
}
return 0, nil
}
_, err := TranslateCPEToCVE(ctx, ds, tempDir, kitlog.NewLogfmtLogger(os.Stdout), false)
require.NoError(t, err)
printMemUsage()
require.Equal(t, []string{tt.cve}, cvesFound)
})
}
t.Run("recent_vulns", func(t *testing.T) {
safeDS := &threadSafeDSMock{Store: ds}
softwareCPEs := []fleet.SoftwareCPE{
{CPE: "cpe:2.3:a:google:chrome:-:*:*:*:*:*:*:*", ID: 1, SoftwareID: 1},
{CPE: "cpe:2.3:a:mozilla:firefox:-:*:*:*:*:*:*:*", ID: 2, SoftwareID: 2},
{CPE: "cpe:2.3:a:haxx:curl:-:*:*:*:*:*:*:*", ID: 3, SoftwareID: 3},
}
ds.ListSoftwareCPEsFunc = func(ctx context.Context, excludedPlatforms []string) ([]fleet.SoftwareCPE, error) {
return softwareCPEs, nil
}
ds.InsertVulnerabilitiesFunc = func(ctx context.Context, vulns []fleet.SoftwareVulnerability, src fleet.VulnerabilitySource) (int64, error) {
return 1, nil
}
recent, err := TranslateCPEToCVE(ctx, safeDS, tempDir, kitlog.NewNopLogger(), true)
require.NoError(t, err)
byCPE := make(map[uint]int)
for _, cpe := range recent {
byCPE[cpe.SoftwareID]++
}
// even if it's somewhat far in the past, I've seen the exact numbers
// change a bit between runs with different downloads, so allow for a bit
// of wiggle room.
assert.Greater(t, byCPE[softwareCPEs[0].SoftwareID], 150, "google chrome CVEs")
assert.Greater(t, byCPE[softwareCPEs[1].SoftwareID], 280, "mozilla firefox CVEs")
assert.Greater(t, byCPE[softwareCPEs[2].SoftwareID], 10, "curl CVEs")
// call it again but now return 0 from this call, simulating CVE-CPE pairs
// that already existed in the DB.
ds.InsertVulnerabilitiesFunc = func(ctx context.Context, vulns []fleet.SoftwareVulnerability, src fleet.VulnerabilitySource) (int64, error) {
return 0, nil
}
recent, err = TranslateCPEToCVE(ctx, safeDS, tempDir, kitlog.NewNopLogger(), true)
require.NoError(t, err)
// no recent vulnerability should be reported
assert.Len(t, recent, 0)
})
}
func TestSyncsCVEFromURL(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasSuffix(r.RequestURI, ".meta") {
fmt.Fprint(w, "lastModifiedDate:2021-08-04T11:10:30-04:00\r\n")
fmt.Fprint(w, "size:20967174\r\n")
fmt.Fprint(w, "zipSize:1453429\r\n")
fmt.Fprint(w, "gzSize:1453293\r\n")
fmt.Fprint(w, "sha256:10D7338A1E2D8DB344C381793110B67FCA7D729ADA21624EF089EBA78CCE7B53\r\n")
}
}))
defer ts.Close()
tempDir := t.TempDir()
cveFeedPrefixURL := ts.URL + "/feeds/json/cve/1.1/"
err := DownloadNVDCVEFeed(tempDir, cveFeedPrefixURL)
require.Error(t, err)
require.Contains(t,
err.Error(),
fmt.Sprintf("1 synchronisation error:\n\tunexpected size for \"%s/feeds/json/cve/1.1/nvdcve-1.1-2002.json.gz\" (200 OK): want 1453293, have 0", ts.URL),
)
}