mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 00:45:19 +00:00
Add visual studio extensions to software inventory (#17501)
#17003 - [X] Changes file added for user-visible changes in `changes/` or `orbit/changes/`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - [X] Added support on fleet's osquery simulator `cmd/osquery-perf` for new osquery data ingestion features. - [x] Added/updated tests - [X] Manual QA for all new/changed functionality
This commit is contained in:
parent
5028722506
commit
cf64d85deb
1
changes/17003-ingest-vscode_extensions
Normal file
1
changes/17003-ingest-vscode_extensions
Normal file
@ -0,0 +1 @@
|
||||
* Visual Studio extensions added to Fleet's software inventory.
|
@ -38,17 +38,21 @@ var (
|
||||
//go:embed *.tmpl
|
||||
templatesFS embed.FS
|
||||
|
||||
//go:embed *.software
|
||||
//go:embed macos_vulnerable.software
|
||||
macOSVulnerableSoftwareFS embed.FS
|
||||
|
||||
//go:embed vscode_extensions_vulnerable.software
|
||||
vsCodeExtensionsVulnerableSoftwareFS embed.FS
|
||||
|
||||
//go:embed ubuntu_2204-software.json.bz2
|
||||
ubuntuSoftwareFS embed.FS
|
||||
//go:embed windows_11-software.json.bz2
|
||||
windowsSoftwareFS embed.FS
|
||||
|
||||
macosVulnerableSoftware []fleet.Software
|
||||
windowsSoftware []map[string]string
|
||||
ubuntuSoftware []map[string]string
|
||||
macosVulnerableSoftware []fleet.Software
|
||||
vsCodeExtensionsVulnerableSoftware []fleet.Software
|
||||
windowsSoftware []map[string]string
|
||||
ubuntuSoftware []map[string]string
|
||||
)
|
||||
|
||||
func loadMacOSVulnerableSoftware() {
|
||||
@ -72,6 +76,28 @@ func loadMacOSVulnerableSoftware() {
|
||||
log.Printf("Loaded %d vulnerable macOS software", len(macosVulnerableSoftware))
|
||||
}
|
||||
|
||||
func loadExtraVulnerableSoftware() {
|
||||
vsCodeExtensionsVulnerableSoftwareData, err := vsCodeExtensionsVulnerableSoftwareFS.ReadFile("vscode_extensions_vulnerable.software")
|
||||
if err != nil {
|
||||
log.Fatal("reading vulnerable vscode_extensions software file: ", err)
|
||||
}
|
||||
lines := bytes.Split(vsCodeExtensionsVulnerableSoftwareData, []byte("\n"))
|
||||
for _, line := range lines {
|
||||
parts := bytes.Split(line, []byte("##"))
|
||||
if len(parts) < 3 {
|
||||
log.Println("skipping", string(line))
|
||||
continue
|
||||
}
|
||||
vsCodeExtensionsVulnerableSoftware = append(vsCodeExtensionsVulnerableSoftware, fleet.Software{
|
||||
Vendor: strings.TrimSpace(string(parts[0])),
|
||||
Name: strings.TrimSpace(string(parts[1])),
|
||||
Version: strings.TrimSpace(string(parts[2])),
|
||||
Source: "vscode_extensions",
|
||||
})
|
||||
}
|
||||
log.Printf("Loaded %d vulnerable vscode_extensions software", len(vsCodeExtensionsVulnerableSoftware))
|
||||
}
|
||||
|
||||
func loadSoftwareItems(fs embed.FS, path string) []map[string]string {
|
||||
bz2, err := fs.Open(path)
|
||||
if err != nil {
|
||||
@ -103,6 +129,7 @@ func loadSoftwareItems(fs embed.FS, path string) []map[string]string {
|
||||
|
||||
func init() {
|
||||
loadMacOSVulnerableSoftware()
|
||||
loadExtraVulnerableSoftware()
|
||||
windowsSoftware = loadSoftwareItems(windowsSoftwareFS, "windows_11-software.json.bz2")
|
||||
ubuntuSoftware = loadSoftwareItems(ubuntuSoftwareFS, "ubuntu_2204-software.json.bz2")
|
||||
}
|
||||
@ -333,21 +360,22 @@ func (n *nodeKeyManager) Add(nodekey string) {
|
||||
}
|
||||
|
||||
type agent struct {
|
||||
agentIndex int
|
||||
softwareCount softwareEntityCount
|
||||
userCount entityCount
|
||||
policyPassProb float64
|
||||
munkiIssueProb float64
|
||||
munkiIssueCount int
|
||||
liveQueryFailProb float64
|
||||
liveQueryNoResultsProb float64
|
||||
strings map[string]string
|
||||
serverAddress string
|
||||
stats *Stats
|
||||
nodeKeyManager *nodeKeyManager
|
||||
nodeKey string
|
||||
templates *template.Template
|
||||
os string
|
||||
agentIndex int
|
||||
softwareCount softwareEntityCount
|
||||
softwareVSCodeExtensionsCount softwareExtraEntityCount
|
||||
userCount entityCount
|
||||
policyPassProb float64
|
||||
munkiIssueProb float64
|
||||
munkiIssueCount int
|
||||
liveQueryFailProb float64
|
||||
liveQueryNoResultsProb float64
|
||||
strings map[string]string
|
||||
serverAddress string
|
||||
stats *Stats
|
||||
nodeKeyManager *nodeKeyManager
|
||||
nodeKey string
|
||||
templates *template.Template
|
||||
os string
|
||||
// deviceAuthToken holds Fleet Desktop device authentication token.
|
||||
//
|
||||
// Non-nil means the agent is identified as orbit osquery,
|
||||
@ -373,6 +401,9 @@ type agent struct {
|
||||
// single goroutine at a time can execute scripts.
|
||||
scriptExecRunning atomic.Bool
|
||||
|
||||
softwareVSCodeExtensionsProb float64
|
||||
softwareVSCodeExtensionsFailProb float64
|
||||
|
||||
//
|
||||
// The following are exported to be used by the templates.
|
||||
//
|
||||
@ -412,13 +443,23 @@ type softwareEntityCount struct {
|
||||
uniqueSoftwareUninstallCount int
|
||||
uniqueSoftwareUninstallProb float64
|
||||
}
|
||||
type softwareExtraEntityCount struct {
|
||||
entityCount
|
||||
commonSoftwareUninstallCount int
|
||||
commonSoftwareUninstallProb float64
|
||||
uniqueSoftwareUninstallCount int
|
||||
uniqueSoftwareUninstallProb float64
|
||||
}
|
||||
|
||||
func newAgent(
|
||||
agentIndex int,
|
||||
serverAddress, enrollSecret string,
|
||||
templates *template.Template,
|
||||
configInterval, logInterval, queryInterval, mdmCheckInInterval time.Duration,
|
||||
softwareQueryFailureProb float64,
|
||||
softwareVSCodeExtensionsQueryFailureProb float64,
|
||||
softwareCount softwareEntityCount,
|
||||
softwareVSCodeExtensionsCount softwareExtraEntityCount,
|
||||
userCount entityCount,
|
||||
policyPassProb float64,
|
||||
orbitProb float64,
|
||||
@ -481,30 +522,34 @@ func newAgent(
|
||||
}
|
||||
|
||||
return &agent{
|
||||
agentIndex: agentIndex,
|
||||
serverAddress: serverAddress,
|
||||
softwareCount: softwareCount,
|
||||
userCount: userCount,
|
||||
strings: make(map[string]string),
|
||||
policyPassProb: policyPassProb,
|
||||
munkiIssueProb: munkiIssueProb,
|
||||
munkiIssueCount: munkiIssueCount,
|
||||
liveQueryFailProb: liveQueryFailProb,
|
||||
liveQueryNoResultsProb: liveQueryNoResultsProb,
|
||||
templates: templates,
|
||||
deviceAuthToken: deviceAuthToken,
|
||||
os: agentOS,
|
||||
agentIndex: agentIndex,
|
||||
serverAddress: serverAddress,
|
||||
softwareCount: softwareCount,
|
||||
softwareVSCodeExtensionsCount: softwareVSCodeExtensionsCount,
|
||||
userCount: userCount,
|
||||
strings: make(map[string]string),
|
||||
policyPassProb: policyPassProb,
|
||||
munkiIssueProb: munkiIssueProb,
|
||||
munkiIssueCount: munkiIssueCount,
|
||||
liveQueryFailProb: liveQueryFailProb,
|
||||
liveQueryNoResultsProb: liveQueryNoResultsProb,
|
||||
templates: templates,
|
||||
deviceAuthToken: deviceAuthToken,
|
||||
os: agentOS,
|
||||
EnrollSecret: enrollSecret,
|
||||
ConfigInterval: configInterval,
|
||||
LogInterval: logInterval,
|
||||
QueryInterval: queryInterval,
|
||||
MDMCheckInInterval: mdmCheckInInterval,
|
||||
UUID: hostUUID,
|
||||
SerialNumber: serialNumber,
|
||||
|
||||
EnrollSecret: enrollSecret,
|
||||
ConfigInterval: configInterval,
|
||||
LogInterval: logInterval,
|
||||
QueryInterval: queryInterval,
|
||||
MDMCheckInInterval: mdmCheckInInterval,
|
||||
UUID: hostUUID,
|
||||
SerialNumber: serialNumber,
|
||||
softwareVSCodeExtensionsProb: softwareQueryFailureProb,
|
||||
softwareVSCodeExtensionsFailProb: softwareVSCodeExtensionsQueryFailureProb,
|
||||
|
||||
macMDMClient: macMDMClient,
|
||||
winMDMClient: winMDMClient,
|
||||
|
||||
macMDMClient: macMDMClient,
|
||||
winMDMClient: winMDMClient,
|
||||
disableScriptExec: disableScriptExec,
|
||||
disableFleetDesktop: disableFleetDesktop,
|
||||
loggerTLSMaxLines: loggerTLSMaxLines,
|
||||
@ -1223,12 +1268,12 @@ func (a *agent) softwareMacOS() []map[string]string {
|
||||
lastOpenedAt = l.Format(time.UnixDate)
|
||||
}
|
||||
commonSoftware[i] = map[string]string{
|
||||
"name": fmt.Sprintf("Common_%d", i),
|
||||
"name": fmt.Sprintf("Common_%d.app", i),
|
||||
"version": "0.0.1",
|
||||
"bundle_identifier": "com.fleetdm.osquery-perf",
|
||||
"source": "osquery-perf",
|
||||
"bundle_identifier": fmt.Sprintf("com.fleetdm.osquery-perf.common_%s_%d", a.CachedString("hostname"), i),
|
||||
"source": "apps",
|
||||
"last_opened_at": lastOpenedAt,
|
||||
"installed_path": fmt.Sprintf("/some/path/Common_%d", i),
|
||||
"installed_path": fmt.Sprintf("/some/path/Common_%d.app", i),
|
||||
}
|
||||
}
|
||||
if a.softwareCount.commonSoftwareUninstallProb > 0.0 && rand.Float64() <= a.softwareCount.commonSoftwareUninstallProb {
|
||||
@ -1244,12 +1289,12 @@ func (a *agent) softwareMacOS() []map[string]string {
|
||||
lastOpenedAt = l.Format(time.UnixDate)
|
||||
}
|
||||
uniqueSoftware[i] = map[string]string{
|
||||
"name": fmt.Sprintf("Unique_%s_%d", a.CachedString("hostname"), i),
|
||||
"name": fmt.Sprintf("Unique_%s_%d.app", a.CachedString("hostname"), i),
|
||||
"version": "1.1.1",
|
||||
"bundle_identifier": "com.fleetdm.osquery-perf",
|
||||
"source": "osquery-perf",
|
||||
"bundle_identifier": fmt.Sprintf("com.fleetdm.osquery-perf.unique_%s_%d", a.CachedString("hostname"), i),
|
||||
"source": "apps",
|
||||
"last_opened_at": lastOpenedAt,
|
||||
"installed_path": fmt.Sprintf("/some/path/Unique_%s_%d", a.CachedString("hostname"), i),
|
||||
"installed_path": fmt.Sprintf("/some/path/Unique_%s_%d.app", a.CachedString("hostname"), i),
|
||||
}
|
||||
}
|
||||
if a.softwareCount.uniqueSoftwareUninstallProb > 0.0 && rand.Float64() <= a.softwareCount.uniqueSoftwareUninstallProb {
|
||||
@ -1282,6 +1327,52 @@ func (a *agent) softwareMacOS() []map[string]string {
|
||||
return software
|
||||
}
|
||||
|
||||
func (a *agent) softwareVSCodeExtensions() []map[string]string {
|
||||
commonVSCodeExtensionsSoftware := make([]map[string]string, a.softwareVSCodeExtensionsCount.common)
|
||||
for i := 0; i < len(commonVSCodeExtensionsSoftware); i++ {
|
||||
commonVSCodeExtensionsSoftware[i] = map[string]string{
|
||||
"name": fmt.Sprintf("common.extension_%d", i),
|
||||
"version": "0.0.1",
|
||||
"source": "vscode_extensions",
|
||||
}
|
||||
}
|
||||
if a.softwareVSCodeExtensionsCount.commonSoftwareUninstallProb > 0.0 && rand.Float64() <= a.softwareCount.commonSoftwareUninstallProb {
|
||||
rand.Shuffle(len(commonVSCodeExtensionsSoftware), func(i, j int) {
|
||||
commonVSCodeExtensionsSoftware[i], commonVSCodeExtensionsSoftware[j] = commonVSCodeExtensionsSoftware[j], commonVSCodeExtensionsSoftware[i]
|
||||
})
|
||||
commonVSCodeExtensionsSoftware = commonVSCodeExtensionsSoftware[:a.softwareVSCodeExtensionsCount.common-a.softwareVSCodeExtensionsCount.commonSoftwareUninstallCount]
|
||||
}
|
||||
uniqueVSCodeExtensionsSoftware := make([]map[string]string, a.softwareVSCodeExtensionsCount.unique)
|
||||
for i := 0; i < len(uniqueVSCodeExtensionsSoftware); i++ {
|
||||
uniqueVSCodeExtensionsSoftware[i] = map[string]string{
|
||||
"name": fmt.Sprintf("unique.extension_%s_%d", a.CachedString("hostname"), i),
|
||||
"version": "1.1.1",
|
||||
"source": "vscode_extensions",
|
||||
}
|
||||
}
|
||||
if a.softwareVSCodeExtensionsCount.uniqueSoftwareUninstallProb > 0.0 && rand.Float64() <= a.softwareVSCodeExtensionsCount.uniqueSoftwareUninstallProb {
|
||||
rand.Shuffle(len(uniqueVSCodeExtensionsSoftware), func(i, j int) {
|
||||
uniqueVSCodeExtensionsSoftware[i], uniqueVSCodeExtensionsSoftware[j] = uniqueVSCodeExtensionsSoftware[j], uniqueVSCodeExtensionsSoftware[i]
|
||||
})
|
||||
uniqueVSCodeExtensionsSoftware = uniqueVSCodeExtensionsSoftware[:a.softwareVSCodeExtensionsCount.unique-a.softwareVSCodeExtensionsCount.uniqueSoftwareUninstallCount]
|
||||
}
|
||||
var vulnerableVSCodeExtensionsSoftware []map[string]string
|
||||
for _, vsCodeExtension := range vsCodeExtensionsVulnerableSoftware {
|
||||
vulnerableVSCodeExtensionsSoftware = append(vulnerableVSCodeExtensionsSoftware, map[string]string{
|
||||
"name": vsCodeExtension.Name,
|
||||
"version": vsCodeExtension.Version,
|
||||
"vendor": vsCodeExtension.Vendor,
|
||||
"source": vsCodeExtension.Source,
|
||||
})
|
||||
}
|
||||
software := append(commonVSCodeExtensionsSoftware, uniqueVSCodeExtensionsSoftware...)
|
||||
software = append(software, vulnerableVSCodeExtensionsSoftware...)
|
||||
rand.Shuffle(len(software), func(i, j int) {
|
||||
software[i], software[j] = software[j], software[i]
|
||||
})
|
||||
return software
|
||||
}
|
||||
|
||||
func (a *agent) DistributedRead() (*distributedReadResponse, error) {
|
||||
request, err := http.NewRequest("POST", a.serverAddress+"/api/osquery/distributed/read", bytes.NewReader([]byte(`{"node_key": "`+a.nodeKey+`"}`)))
|
||||
if err != nil {
|
||||
@ -1574,6 +1665,7 @@ func (a *agent) processQuery(name, query string) (
|
||||
)
|
||||
statusOK := fleet.StatusOK
|
||||
statusNotOK := fleet.OsqueryStatus(1)
|
||||
results = []map[string]string{} // if a query fails, osquery returns empty results array
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(name, liveQueryPrefix):
|
||||
@ -1624,19 +1716,28 @@ func (a *agent) processQuery(name, query string) (
|
||||
}
|
||||
return true, results, &ss, nil, nil
|
||||
case name == hostDetailQueryPrefix+"software_macos":
|
||||
ss := fleet.OsqueryStatus(rand.Intn(2))
|
||||
ss := fleet.StatusOK
|
||||
if a.softwareVSCodeExtensionsProb > 0.0 && rand.Float64() <= a.softwareVSCodeExtensionsProb {
|
||||
ss = fleet.OsqueryStatus(1)
|
||||
}
|
||||
if ss == fleet.StatusOK {
|
||||
results = a.softwareMacOS()
|
||||
}
|
||||
return true, results, &ss, nil, nil
|
||||
case name == hostDetailQueryPrefix+"software_windows":
|
||||
ss := fleet.OsqueryStatus(rand.Intn(2))
|
||||
ss := fleet.StatusOK
|
||||
if a.softwareVSCodeExtensionsProb > 0.0 && rand.Float64() <= a.softwareVSCodeExtensionsProb {
|
||||
ss = fleet.OsqueryStatus(1)
|
||||
}
|
||||
if ss == fleet.StatusOK {
|
||||
results = windowsSoftware
|
||||
}
|
||||
return true, results, &ss, nil, nil
|
||||
case name == hostDetailQueryPrefix+"software_linux":
|
||||
ss := fleet.OsqueryStatus(rand.Intn(2))
|
||||
ss := fleet.StatusOK
|
||||
if a.softwareVSCodeExtensionsProb > 0.0 && rand.Float64() <= a.softwareVSCodeExtensionsProb {
|
||||
ss = fleet.OsqueryStatus(1)
|
||||
}
|
||||
if ss == fleet.StatusOK {
|
||||
switch a.os {
|
||||
case "ubuntu":
|
||||
@ -1644,6 +1745,15 @@ func (a *agent) processQuery(name, query string) (
|
||||
}
|
||||
}
|
||||
return true, results, &ss, nil, nil
|
||||
case name == hostDetailQueryPrefix+"software_vscode_extensions":
|
||||
ss := fleet.StatusOK
|
||||
if a.softwareVSCodeExtensionsFailProb > 0.0 && rand.Float64() <= a.softwareVSCodeExtensionsFailProb {
|
||||
ss = fleet.OsqueryStatus(1)
|
||||
}
|
||||
if ss == fleet.StatusOK {
|
||||
results = a.softwareVSCodeExtensions()
|
||||
}
|
||||
return true, results, &ss, nil, nil
|
||||
case name == hostDetailQueryPrefix+"disk_space_unix" || name == hostDetailQueryPrefix+"disk_space_windows":
|
||||
ss := fleet.OsqueryStatus(rand.Intn(2))
|
||||
if ss == fleet.StatusOK {
|
||||
@ -1889,13 +1999,22 @@ func main() {
|
||||
onlyAlreadyEnrolled = flag.Bool("only_already_enrolled", false, "Only start agents that are already enrolled")
|
||||
nodeKeyFile = flag.String("node_key_file", "", "File with node keys to use")
|
||||
|
||||
commonSoftwareCount = flag.Int("common_software_count", 10, "Number of common installed applications reported to fleet")
|
||||
commonSoftwareUninstallCount = flag.Int("common_software_uninstall_count", 1, "Number of common software to uninstall")
|
||||
commonSoftwareUninstallProb = flag.Float64("common_software_uninstall_prob", 0.1, "Probability of uninstalling common_software_uninstall_count unique software/s")
|
||||
softwareQueryFailureProb = flag.Float64("software_query_fail_prob", 0.5, "Probability of the software query failing")
|
||||
softwareVSCodeExtensionsQueryFailureProb = flag.Float64("software_vscode_extensions_query_fail_prob", 0.5, "Probability of the software vscode_extensions query failing")
|
||||
|
||||
uniqueSoftwareCount = flag.Int("unique_software_count", 1, "Number of unique software installed on each host")
|
||||
uniqueSoftwareUninstallCount = flag.Int("unique_software_uninstall_count", 1, "Number of unique software to uninstall")
|
||||
uniqueSoftwareUninstallProb = flag.Float64("unique_software_uninstall_prob", 0.1, "Probability of uninstalling unique_software_uninstall_count common software/s")
|
||||
commonSoftwareCount = flag.Int("common_software_count", 10, "Number of common installed applications reported to fleet")
|
||||
commonVSCodeExtensionsSoftwareCount = flag.Int("common_vscode_extensions_software_count", 5, "Number of common vscode_extensions installed applications reported to fleet")
|
||||
commonSoftwareUninstallCount = flag.Int("common_software_uninstall_count", 1, "Number of common software to uninstall")
|
||||
commonVSCodeExtensionsSoftwareUninstallCount = flag.Int("common_vscode_extensions_software_uninstall_count", 1, "Number of common vscode_extensions software to uninstall")
|
||||
commonSoftwareUninstallProb = flag.Float64("common_software_uninstall_prob", 0.1, "Probability of uninstalling common_software_uninstall_count unique software/s")
|
||||
commonVSCodeExtensionsSoftwareUninstallProb = flag.Float64("common_vscode_extensions_software_uninstall_prob", 0.1, "Probability of uninstalling vscode_extensions common_software_uninstall_count unique software/s")
|
||||
|
||||
uniqueSoftwareCount = flag.Int("unique_software_count", 1, "Number of unique software installed on each host")
|
||||
uniqueVSCodeExtensionsSoftwareCount = flag.Int("unique_vscode_extensions_software_count", 1, "Number of unique vscode_extensions software installed on each host")
|
||||
uniqueSoftwareUninstallCount = flag.Int("unique_software_uninstall_count", 1, "Number of unique software to uninstall")
|
||||
uniqueVSCodeExtensionsSoftwareUninstallCount = flag.Int("unique_vscode_extensions_software_uninstall_count", 1, "Number of unique vscode_extensions software to uninstall")
|
||||
uniqueSoftwareUninstallProb = flag.Float64("unique_software_uninstall_prob", 0.1, "Probability of uninstalling unique_software_uninstall_count common software/s")
|
||||
uniqueVSCodeExtensionsSoftwareUninstallProb = flag.Float64("unique_vscode_extensions_software_uninstall_prob", 0.1, "Probability of uninstalling unique_vscode_extensions_software_uninstall_count common software/s")
|
||||
|
||||
vulnerableSoftwareCount = flag.Int("vulnerable_software_count", 10, "Number of vulnerable installed applications reported to fleet")
|
||||
withLastOpenedSoftwareCount = flag.Int("with_last_opened_software_count", 10, "Number of applications that may report a last opened timestamp to fleet")
|
||||
@ -2016,6 +2135,8 @@ func main() {
|
||||
*logInterval,
|
||||
*queryInterval,
|
||||
*mdmCheckInInterval,
|
||||
*softwareQueryFailureProb,
|
||||
*softwareVSCodeExtensionsQueryFailureProb,
|
||||
softwareEntityCount{
|
||||
entityCount: entityCount{
|
||||
common: *commonSoftwareCount,
|
||||
@ -2028,7 +2149,18 @@ func main() {
|
||||
commonSoftwareUninstallProb: *commonSoftwareUninstallProb,
|
||||
uniqueSoftwareUninstallCount: *uniqueSoftwareUninstallCount,
|
||||
uniqueSoftwareUninstallProb: *uniqueSoftwareUninstallProb,
|
||||
}, entityCount{
|
||||
},
|
||||
softwareExtraEntityCount{
|
||||
entityCount: entityCount{
|
||||
common: *commonVSCodeExtensionsSoftwareCount,
|
||||
unique: *uniqueVSCodeExtensionsSoftwareCount,
|
||||
},
|
||||
commonSoftwareUninstallCount: *commonVSCodeExtensionsSoftwareUninstallCount,
|
||||
commonSoftwareUninstallProb: *commonVSCodeExtensionsSoftwareUninstallProb,
|
||||
uniqueSoftwareUninstallCount: *uniqueVSCodeExtensionsSoftwareUninstallCount,
|
||||
uniqueSoftwareUninstallProb: *uniqueVSCodeExtensionsSoftwareUninstallProb,
|
||||
},
|
||||
entityCount{
|
||||
common: *commonUserCount,
|
||||
unique: *uniqueUserCount,
|
||||
},
|
||||
|
5
cmd/osquery-perf/vscode_extensions_vulnerable.software
Normal file
5
cmd/osquery-perf/vscode_extensions_vulnerable.software
Normal file
@ -0,0 +1,5 @@
|
||||
Microsoft##ms-vscode-remote.remote-wsl##0.63.10
|
||||
GitHub##github.vscode-pull-request-github##0.66.1
|
||||
Microsoft##ms-python.python##2020.4.0
|
||||
Microsoft##ms-toolsai.jupyter##2023.10.10
|
||||
Microsoft##dbaeumer.vscode-eslint##2.0.0
|
@ -952,6 +952,8 @@ func (svc *Service) SubmitDistributedQueryResults(
|
||||
|
||||
svc.maybeDebugHost(ctx, host, results, statuses, messages, stats)
|
||||
|
||||
preProcessSoftwareResults(host.ID, &results, &statuses, &messages, svc.logger)
|
||||
|
||||
var hostWithoutPolicies bool
|
||||
for query, rows := range results {
|
||||
// When receiving this query in the results, we will update the host's
|
||||
@ -1091,6 +1093,74 @@ func (svc *Service) SubmitDistributedQueryResults(
|
||||
return nil
|
||||
}
|
||||
|
||||
// preProcessSoftwareResults will run pre-processing on the responses of the software queries.
|
||||
// It will move the results from the software extra queries (e.g. software_vscode_extensions)
|
||||
// into the main software query results (software_{macos|linux|windows}).
|
||||
// We do this to not grow the main software queries and to ingest
|
||||
// all software together (one direct ingest function for all software).
|
||||
func preProcessSoftwareResults(
|
||||
hostID uint,
|
||||
results *fleet.OsqueryDistributedQueryResults,
|
||||
statuses *map[string]fleet.OsqueryStatus,
|
||||
messages *map[string]string,
|
||||
logger log.Logger,
|
||||
) {
|
||||
vsCodeExtensionsExtraQuery := hostDetailQueryPrefix + "software_vscode_extensions"
|
||||
preProcessSoftwareExtraResults(vsCodeExtensionsExtraQuery, hostID, results, statuses, messages, logger)
|
||||
}
|
||||
|
||||
func preProcessSoftwareExtraResults(
|
||||
softwareExtraQuery string,
|
||||
hostID uint,
|
||||
results *fleet.OsqueryDistributedQueryResults,
|
||||
statuses *map[string]fleet.OsqueryStatus,
|
||||
messages *map[string]string,
|
||||
logger log.Logger,
|
||||
) {
|
||||
// We always remove the extra query and its results
|
||||
// in case the main or extra software query failed to execute.
|
||||
defer delete(*results, softwareExtraQuery)
|
||||
|
||||
status, ok := (*statuses)[softwareExtraQuery]
|
||||
if !ok {
|
||||
return // query did not execute, e.g. the table does not exist.
|
||||
}
|
||||
failed := status != fleet.StatusOK
|
||||
if failed {
|
||||
// extra query executed but with errors, so we return without changing anything.
|
||||
level.Error(logger).Log(
|
||||
"query", softwareExtraQuery,
|
||||
"message", (*messages)[softwareExtraQuery],
|
||||
"hostID", hostID,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract the results of the extra query.
|
||||
softwareExtraRows, _ := (*results)[softwareExtraQuery]
|
||||
if len(softwareExtraRows) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Append the results of the extra query to the main query.
|
||||
for _, query := range []string{
|
||||
// Only one of these execute in each host.
|
||||
hostDetailQueryPrefix + "software_macos",
|
||||
hostDetailQueryPrefix + "software_windows",
|
||||
hostDetailQueryPrefix + "software_linux",
|
||||
} {
|
||||
if _, ok := (*results)[query]; !ok {
|
||||
continue
|
||||
}
|
||||
if status, ok := (*statuses)[query]; ok && status != fleet.StatusOK {
|
||||
// Do not append results if the main query failed to run.
|
||||
continue
|
||||
}
|
||||
(*results)[query] = append((*results)[query], softwareExtraRows...)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// globalPolicyAutomationsEnabled returns true if any of the global policy automations are enabled.
|
||||
// globalPolicyAutomationsEnabled and teamPolicyAutomationsEnabled are effectively identical.
|
||||
// We could not use Go generics because Go generics does not support accessing common struct fields right now.
|
||||
|
@ -841,8 +841,7 @@ func TestSubmitResultLogsToQueryResultsDoesNotCountNullDataRows(t *testing.T) {
|
||||
assert.True(t, ds.OverwriteQueryResultRowsFuncInvoked)
|
||||
}
|
||||
|
||||
type failingLogger struct {
|
||||
}
|
||||
type failingLogger struct{}
|
||||
|
||||
func (n *failingLogger) Write(context.Context, []json.RawMessage) error {
|
||||
return errors.New("some error")
|
||||
@ -894,7 +893,6 @@ func TestSubmitResultLogsFail(t *testing.T) {
|
||||
err = svc.SubmitResultLogs(ctx, results)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, http.StatusRequestEntityTooLarge, err.(*osqueryError).Status())
|
||||
|
||||
}
|
||||
|
||||
func TestGetQueryNameAndTeamIDFromResult(t *testing.T) {
|
||||
@ -1005,12 +1003,13 @@ func verifyDiscovery(t *testing.T, queries, discovery map[string]string) {
|
||||
assert.Equal(t, len(queries), len(discovery))
|
||||
// discoveryUsed holds the queries where we know use the distributed discovery feature.
|
||||
discoveryUsed := map[string]struct{}{
|
||||
hostDetailQueryPrefix + "google_chrome_profiles": {},
|
||||
hostDetailQueryPrefix + "mdm": {},
|
||||
hostDetailQueryPrefix + "munki_info": {},
|
||||
hostDetailQueryPrefix + "windows_update_history": {},
|
||||
hostDetailQueryPrefix + "kubequery_info": {},
|
||||
hostDetailQueryPrefix + "orbit_info": {},
|
||||
hostDetailQueryPrefix + "google_chrome_profiles": {},
|
||||
hostDetailQueryPrefix + "mdm": {},
|
||||
hostDetailQueryPrefix + "munki_info": {},
|
||||
hostDetailQueryPrefix + "windows_update_history": {},
|
||||
hostDetailQueryPrefix + "kubequery_info": {},
|
||||
hostDetailQueryPrefix + "orbit_info": {},
|
||||
hostDetailQueryPrefix + "software_vscode_extensions": {},
|
||||
}
|
||||
for name := range queries {
|
||||
require.NotEmpty(t, discovery[name])
|
||||
@ -1703,8 +1702,9 @@ func TestDetailQueries(t *testing.T) {
|
||||
// queries)
|
||||
queries, discovery, acc, err := svc.GetDistributedQueries(ctx)
|
||||
require.NoError(t, err)
|
||||
// +1 for software inventory, +1 for fleet_no_policies_wildcard
|
||||
if expected := expectedDetailQueriesForPlatform(host.Platform); !assert.Equal(t, len(expected)+1+1, len(queries)) {
|
||||
// +2 for software inventory (+1 for the main software query +1 software_vscode_extensions)
|
||||
// +1 for fleet_no_policies_wildcard
|
||||
if expected := expectedDetailQueriesForPlatform(host.Platform); !assert.Equal(t, len(expected)+2+1, len(queries)) {
|
||||
// this is just to print the diff between the expected and actual query
|
||||
// keys when the count assertion fails, to help debugging - they are not
|
||||
// expected to match.
|
||||
@ -1968,8 +1968,9 @@ func TestDetailQueries(t *testing.T) {
|
||||
|
||||
queries, discovery, acc, err = svc.GetDistributedQueries(ctx)
|
||||
require.NoError(t, err)
|
||||
// +1 software inventory, +1 fleet_no_policies_wildcard query
|
||||
require.Equal(t, len(expectedDetailQueriesForPlatform(host.Platform))+1+1, len(queries), distQueriesMapKeys(queries))
|
||||
// +2 software inventory (+1 main software query and +1 software extra query )
|
||||
// +1 fleet_no_policies_wildcard query
|
||||
require.Equal(t, len(expectedDetailQueriesForPlatform(host.Platform))+2+1, len(queries), distQueriesMapKeys(queries))
|
||||
verifyDiscovery(t, queries, discovery)
|
||||
assert.Zero(t, acc)
|
||||
}
|
||||
@ -3506,3 +3507,254 @@ func osqueryMapKeys(m map[string]osquery_utils.DetailQuery) []string {
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
func TestPreProcessSoftwareResults(t *testing.T) {
|
||||
foobarApp := map[string]string{
|
||||
"name": "Foobar.app",
|
||||
"version": "1.2.3",
|
||||
"type": "Application (macOS)",
|
||||
"bundle_identifier": "com.zoobar.foobar",
|
||||
"extension_id": "",
|
||||
"browser": "",
|
||||
"source": "apps",
|
||||
"vendor": "",
|
||||
"last_opened_at": "0",
|
||||
"installed_path": "/some/path",
|
||||
}
|
||||
zoobarApp := map[string]string{
|
||||
"name": "Zoobar.app",
|
||||
"version": "3.2.1",
|
||||
"type": "Application (macOS)",
|
||||
"bundle_identifier": "com.acme.zoobar",
|
||||
"extension_id": "",
|
||||
"browser": "",
|
||||
"source": "apps",
|
||||
"vendor": "",
|
||||
"last_opened_at": "0",
|
||||
"installed_path": "/some/other/path",
|
||||
}
|
||||
foobarVSCodeExtension := map[string]string{
|
||||
"name": "vendor-x.foobar",
|
||||
"version": "2024.2.1",
|
||||
"type": "IDE extension (VS Code)",
|
||||
"bundle_identifier": "",
|
||||
"extension_id": "",
|
||||
"browser": "",
|
||||
"source": "vscode_extensions",
|
||||
"vendor": "VendorX",
|
||||
"last_opened_at": "",
|
||||
"installed_path": "/some/foobar/path",
|
||||
}
|
||||
zoobarVSCodeExtension := map[string]string{
|
||||
"name": "vendor-x.zoobar",
|
||||
"version": "2023.2.1",
|
||||
"type": "IDE extension (VS Code)",
|
||||
"bundle_identifier": "",
|
||||
"extension_id": "",
|
||||
"browser": "",
|
||||
"source": "vscode_extensions",
|
||||
"vendor": "VendorX",
|
||||
"last_opened_at": "",
|
||||
"installed_path": "/some/zoobar/path",
|
||||
}
|
||||
someRow := map[string]string{
|
||||
"1": "1",
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
|
||||
resultsIn fleet.OsqueryDistributedQueryResults
|
||||
statusesIn map[string]fleet.OsqueryStatus
|
||||
messagesIn map[string]string
|
||||
|
||||
resultsOut fleet.OsqueryDistributedQueryResults
|
||||
}{
|
||||
{
|
||||
name: "software query works and there are vs code extensions in extra",
|
||||
|
||||
statusesIn: map[string]fleet.OsqueryStatus{
|
||||
hostDetailQueryPrefix + "other_detail_query": fleet.StatusOK,
|
||||
hostDetailQueryPrefix + "software_macos": fleet.StatusOK,
|
||||
hostDetailQueryPrefix + "software_vscode_extensions": fleet.StatusOK,
|
||||
},
|
||||
resultsIn: fleet.OsqueryDistributedQueryResults{
|
||||
hostDetailQueryPrefix + "other_detail_query": []map[string]string{
|
||||
someRow,
|
||||
},
|
||||
hostDetailQueryPrefix + "software_macos": []map[string]string{
|
||||
foobarApp,
|
||||
zoobarApp,
|
||||
},
|
||||
hostDetailQueryPrefix + "software_vscode_extensions": []map[string]string{
|
||||
foobarVSCodeExtension,
|
||||
zoobarVSCodeExtension,
|
||||
},
|
||||
},
|
||||
|
||||
resultsOut: fleet.OsqueryDistributedQueryResults{
|
||||
hostDetailQueryPrefix + "other_detail_query": []map[string]string{
|
||||
someRow,
|
||||
},
|
||||
hostDetailQueryPrefix + "software_macos": []map[string]string{
|
||||
foobarApp,
|
||||
zoobarApp,
|
||||
foobarVSCodeExtension,
|
||||
zoobarVSCodeExtension,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "software query and extra works and there are no vscode extensions",
|
||||
|
||||
statusesIn: map[string]fleet.OsqueryStatus{
|
||||
hostDetailQueryPrefix + "other_detail_query": fleet.StatusOK,
|
||||
hostDetailQueryPrefix + "software_macos": fleet.StatusOK,
|
||||
hostDetailQueryPrefix + "software_vscode_extensions": fleet.StatusOK,
|
||||
},
|
||||
resultsIn: fleet.OsqueryDistributedQueryResults{
|
||||
hostDetailQueryPrefix + "other_detail_query": []map[string]string{
|
||||
someRow,
|
||||
},
|
||||
hostDetailQueryPrefix + "software_macos": []map[string]string{
|
||||
foobarApp,
|
||||
zoobarApp,
|
||||
},
|
||||
hostDetailQueryPrefix + "software_vscode_extensions": []map[string]string{},
|
||||
},
|
||||
|
||||
resultsOut: fleet.OsqueryDistributedQueryResults{
|
||||
hostDetailQueryPrefix + "other_detail_query": []map[string]string{
|
||||
someRow,
|
||||
},
|
||||
hostDetailQueryPrefix + "software_macos": []map[string]string{
|
||||
foobarApp,
|
||||
zoobarApp,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "software query works and the software extra status and results are not returned",
|
||||
|
||||
statusesIn: map[string]fleet.OsqueryStatus{
|
||||
hostDetailQueryPrefix + "other_detail_query": fleet.StatusOK,
|
||||
hostDetailQueryPrefix + "software_macos": fleet.StatusOK,
|
||||
},
|
||||
resultsIn: fleet.OsqueryDistributedQueryResults{
|
||||
hostDetailQueryPrefix + "other_detail_query": []map[string]string{
|
||||
someRow,
|
||||
},
|
||||
hostDetailQueryPrefix + "software_macos": []map[string]string{
|
||||
foobarApp,
|
||||
zoobarApp,
|
||||
},
|
||||
},
|
||||
|
||||
resultsOut: fleet.OsqueryDistributedQueryResults{
|
||||
hostDetailQueryPrefix + "other_detail_query": []map[string]string{
|
||||
someRow,
|
||||
},
|
||||
hostDetailQueryPrefix + "software_macos": []map[string]string{
|
||||
foobarApp,
|
||||
zoobarApp,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "software doesn't return status or results but the software extra does",
|
||||
|
||||
statusesIn: map[string]fleet.OsqueryStatus{
|
||||
hostDetailQueryPrefix + "software_vscode_extensions": fleet.StatusOK,
|
||||
},
|
||||
resultsIn: fleet.OsqueryDistributedQueryResults{
|
||||
hostDetailQueryPrefix + "software_vscode_extensions": []map[string]string{
|
||||
foobarVSCodeExtension,
|
||||
zoobarVSCodeExtension,
|
||||
},
|
||||
},
|
||||
|
||||
resultsOut: fleet.OsqueryDistributedQueryResults{},
|
||||
},
|
||||
{
|
||||
name: "software query works, but vscode_extensions table doesn't exist",
|
||||
|
||||
statusesIn: map[string]fleet.OsqueryStatus{
|
||||
hostDetailQueryPrefix + "software_macos": fleet.StatusOK,
|
||||
hostDetailQueryPrefix + "software_vscode_extensions": fleet.OsqueryStatus(1),
|
||||
},
|
||||
resultsIn: fleet.OsqueryDistributedQueryResults{
|
||||
hostDetailQueryPrefix + "software_macos": []map[string]string{
|
||||
foobarApp,
|
||||
zoobarApp,
|
||||
},
|
||||
hostDetailQueryPrefix + "software_vscode_extensions": []map[string]string{},
|
||||
},
|
||||
|
||||
resultsOut: fleet.OsqueryDistributedQueryResults{
|
||||
hostDetailQueryPrefix + "software_macos": []map[string]string{
|
||||
foobarApp,
|
||||
zoobarApp,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "software query fails, vscode_extensions table returns results",
|
||||
|
||||
statusesIn: map[string]fleet.OsqueryStatus{
|
||||
hostDetailQueryPrefix + "software_macos": fleet.OsqueryStatus(1),
|
||||
hostDetailQueryPrefix + "software_vscode_extensions": fleet.StatusOK,
|
||||
},
|
||||
resultsIn: fleet.OsqueryDistributedQueryResults{
|
||||
hostDetailQueryPrefix + "software_macos": []map[string]string{},
|
||||
hostDetailQueryPrefix + "software_vscode_extensions": []map[string]string{
|
||||
foobarVSCodeExtension,
|
||||
zoobarVSCodeExtension,
|
||||
},
|
||||
},
|
||||
|
||||
resultsOut: fleet.OsqueryDistributedQueryResults{
|
||||
hostDetailQueryPrefix + "software_macos": []map[string]string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "software query fails, software extra query also fails",
|
||||
|
||||
statusesIn: map[string]fleet.OsqueryStatus{
|
||||
hostDetailQueryPrefix + "software_macos": fleet.OsqueryStatus(1),
|
||||
hostDetailQueryPrefix + "software_vscode_extensions": fleet.OsqueryStatus(1),
|
||||
},
|
||||
resultsIn: fleet.OsqueryDistributedQueryResults{
|
||||
hostDetailQueryPrefix + "software_macos": []map[string]string{},
|
||||
hostDetailQueryPrefix + "software_vscode_extensions": []map[string]string{},
|
||||
},
|
||||
|
||||
resultsOut: fleet.OsqueryDistributedQueryResults{
|
||||
hostDetailQueryPrefix + "software_macos": []map[string]string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "software inventory turned off",
|
||||
|
||||
statusesIn: map[string]fleet.OsqueryStatus{
|
||||
hostDetailQueryPrefix + "other_detail_query": fleet.StatusOK,
|
||||
},
|
||||
resultsIn: fleet.OsqueryDistributedQueryResults{
|
||||
hostDetailQueryPrefix + "other_detail_query": []map[string]string{
|
||||
someRow,
|
||||
},
|
||||
},
|
||||
|
||||
resultsOut: fleet.OsqueryDistributedQueryResults{
|
||||
hostDetailQueryPrefix + "other_detail_query": []map[string]string{
|
||||
someRow,
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
preProcessSoftwareResults(1, &tc.resultsIn, &tc.statusesIn, &tc.messagesIn, log.NewNopLogger())
|
||||
require.Equal(t, tc.resultsOut, tc.resultsIn)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -722,6 +722,7 @@ SELECT
|
||||
'' AS extension_id,
|
||||
'' AS browser,
|
||||
'apps' AS source,
|
||||
'' AS vendor,
|
||||
last_opened_time AS last_opened_at,
|
||||
path AS installed_path
|
||||
FROM apps
|
||||
@ -734,6 +735,7 @@ SELECT
|
||||
'' AS extension_id,
|
||||
'' AS browser,
|
||||
'python_packages' AS source,
|
||||
'' AS vendor,
|
||||
0 AS last_opened_at,
|
||||
path AS installed_path
|
||||
FROM python_packages
|
||||
@ -746,6 +748,7 @@ SELECT
|
||||
identifier AS extension_id,
|
||||
browser_type AS browser,
|
||||
'chrome_extensions' AS source,
|
||||
'' AS vendor,
|
||||
0 AS last_opened_at,
|
||||
path AS installed_path
|
||||
FROM cached_users CROSS JOIN chrome_extensions USING (uid)
|
||||
@ -758,6 +761,7 @@ SELECT
|
||||
identifier AS extension_id,
|
||||
'firefox' AS browser,
|
||||
'firefox_addons' AS source,
|
||||
'' AS vendor,
|
||||
0 AS last_opened_at,
|
||||
path AS installed_path
|
||||
FROM cached_users CROSS JOIN firefox_addons USING (uid)
|
||||
@ -770,6 +774,7 @@ SELECT
|
||||
'' AS extension_id,
|
||||
'' AS browser,
|
||||
'safari_extensions' AS source,
|
||||
'' AS vendor,
|
||||
0 AS last_opened_at,
|
||||
path AS installed_path
|
||||
FROM cached_users CROSS JOIN safari_extensions USING (uid)
|
||||
@ -782,6 +787,7 @@ SELECT
|
||||
'' AS extension_id,
|
||||
'' AS browser,
|
||||
'homebrew_packages' AS source,
|
||||
'' AS vendor,
|
||||
0 AS last_opened_at,
|
||||
path AS installed_path
|
||||
FROM homebrew_packages;
|
||||
@ -790,6 +796,30 @@ FROM homebrew_packages;
|
||||
DirectIngestFunc: directIngestSoftware,
|
||||
}
|
||||
|
||||
// softwareVSCodeExtensions collects VSCode extensions on a separate query for two reasons:
|
||||
// - vscode_extensions is not available in osquery < 5.11.0.
|
||||
// - Avoid growing the main `software_{macos|windows|linux}` queries
|
||||
// (having big queries can cause performance issues or be denylisted).
|
||||
var softwareVSCodeExtensions = DetailQuery{
|
||||
Query: withCachedUsers(`WITH cached_users AS (%s)
|
||||
SELECT
|
||||
name,
|
||||
version,
|
||||
'IDE extension (VS Code)' AS type,
|
||||
'' AS bundle_identifier,
|
||||
uuid AS extension_id,
|
||||
'' AS browser,
|
||||
'vscode_extensions' AS source,
|
||||
publisher AS vendor,
|
||||
'' AS last_opened_at,
|
||||
path AS installed_path
|
||||
FROM cached_users CROSS JOIN vscode_extensions USING (uid)`),
|
||||
Platforms: append(fleet.HostLinuxOSs, "darwin", "windows"),
|
||||
Discovery: discoveryTable("vscode_extensions"),
|
||||
// Has no IngestFunc, DirectIngestFunc or DirectTaskIngestFunc because
|
||||
// the results of this query are appended to the results of the other software queries.
|
||||
}
|
||||
|
||||
var scheduledQueryStats = DetailQuery{
|
||||
Query: `
|
||||
SELECT *,
|
||||
@ -1829,6 +1859,7 @@ func GetDetailQueries(
|
||||
generatedMap["software_linux"] = softwareLinux
|
||||
generatedMap["software_windows"] = softwareWindows
|
||||
generatedMap["software_chrome"] = softwareChrome
|
||||
generatedMap["software_vscode_extensions"] = softwareVSCodeExtensions
|
||||
}
|
||||
|
||||
if features != nil && features.EnableHostUsers {
|
||||
|
@ -290,7 +290,7 @@ func TestGetDetailQueries(t *testing.T) {
|
||||
sortedKeysCompare(t, queriesWithUsers, qs)
|
||||
|
||||
queriesWithUsersAndSoftware := GetDetailQueries(context.Background(), config.FleetConfig{App: config.AppConfig{EnableScheduledQueryStats: true}}, nil, &fleet.Features{EnableHostUsers: true, EnableSoftwareInventory: true})
|
||||
qs = append(baseQueries, "users", "users_chrome", "software_macos", "software_linux", "software_windows", "software_chrome", "scheduled_query_stats")
|
||||
qs = append(baseQueries, "users", "users_chrome", "software_macos", "software_linux", "software_windows", "software_vscode_extensions", "software_chrome", "scheduled_query_stats")
|
||||
require.Len(t, queriesWithUsersAndSoftware, len(qs))
|
||||
sortedKeysCompare(t, queriesWithUsersAndSoftware, qs)
|
||||
|
||||
|
@ -10,7 +10,7 @@ To test these changes locally, you can:
|
||||
2. host this file on a local web server
|
||||
|
||||
```bash
|
||||
go run ./tools/file-server 8082 ./server/vulnerabilities/nvd
|
||||
go run ./tools/file-server/main.go 8082 ./server/vulnerabilities/nvd/
|
||||
```
|
||||
|
||||
3. (re)launch your local fleet server with one of the following
|
||||
@ -20,7 +20,6 @@ To test these changes locally, you can:
|
||||
vulnerabilities:
|
||||
cpe_translations_url: "http://localhost:8082/cpe_translations.json"
|
||||
```
|
||||
|
||||
|
||||
Environment method
|
||||
```bash
|
||||
@ -28,7 +27,6 @@ To test these changes locally, you can:
|
||||
```
|
||||
|
||||
4. trigger a vulnerabilities scan
|
||||
|
||||
```bash
|
||||
fleetctl trigger --name vulnerabilities
|
||||
```
|
||||
|
@ -41,6 +41,8 @@ type CPEMatchingRule struct {
|
||||
CVEs map[string]struct{}
|
||||
// IgnoreAll will cause all CPEs to not match hence ignoring a CVE.
|
||||
IgnoreAll bool
|
||||
// IgnoreIf is a function that can determine if a CPE matching rule should be ignored or not.
|
||||
IgnoreIf func(cpeMeta *wfn.Attributes) bool
|
||||
}
|
||||
|
||||
// CPEMatches returns true if the provided CPE matches the rule.
|
||||
@ -53,6 +55,10 @@ func (rule CPEMatchingRule) CPEMatches(cpeMeta *wfn.Attributes) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if rule.IgnoreIf != nil && rule.IgnoreIf(cpeMeta) {
|
||||
return false
|
||||
}
|
||||
|
||||
ver, err := semver.NewVersion(wfn.StripSlashes(cpeMeta.Version))
|
||||
if err != nil {
|
||||
return false
|
||||
|
@ -2,6 +2,8 @@ package nvd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/facebookincubator/nvdtools/wfn"
|
||||
)
|
||||
|
||||
type CPEMatchingRules []CPEMatchingRule
|
||||
@ -181,6 +183,20 @@ func GetKnownNVDBugRules() (CPEMatchingRules, error) {
|
||||
},
|
||||
},
|
||||
},
|
||||
// These vulnerabilities in the MongoDB client incorrectly match
|
||||
// the VS Code extension.
|
||||
CPEMatchingRule{
|
||||
CVEs: map[string]struct{}{
|
||||
"CVE-2012-6619": {},
|
||||
"CVE-2013-1892": {},
|
||||
"CVE-2013-2132": {},
|
||||
"CVE-2015-1609": {},
|
||||
"CVE-2016-6494": {},
|
||||
},
|
||||
IgnoreIf: func(cpeMeta *wfn.Attributes) bool {
|
||||
return cpeMeta.TargetSW == "visual_studio_code"
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, rule := range rules {
|
||||
|
@ -466,8 +466,6 @@ func TestLegacyCPEDB(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCPEFromSoftwareIntegration(t *testing.T) {
|
||||
nettest.Run(t)
|
||||
|
||||
testCases := []struct {
|
||||
software fleet.Software
|
||||
cpe string
|
||||
@ -1334,6 +1332,245 @@ func TestCPEFromSoftwareIntegration(t *testing.T) {
|
||||
},
|
||||
cpe: "cpe:2.3:a:jetbrains:pycharm:2022.1:*:*:*:*:macos:*:*",
|
||||
},
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "eamodio.gitlens",
|
||||
Source: "vscode_extensions",
|
||||
Version: "14.9.0",
|
||||
Vendor: "GitKraken",
|
||||
},
|
||||
cpe: "cpe:2.3:a:gitkraken:gitlens:14.9.0:*:*:*:*:visual_studio_code:*:*",
|
||||
},
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "ms-python.python",
|
||||
Source: "vscode_extensions",
|
||||
Version: "2024.2.1",
|
||||
Vendor: "Microsoft",
|
||||
},
|
||||
cpe: "cpe:2.3:a:microsoft:python_extension:2024.2.1:*:*:*:*:visual_studio_code:*:*",
|
||||
},
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "ms-toolsai.jupyter",
|
||||
Source: "vscode_extensions",
|
||||
Version: "2024.2.0",
|
||||
Vendor: "Microsoft",
|
||||
},
|
||||
cpe: "cpe:2.3:a:microsoft:jupyter:2024.2.0:*:*:*:*:visual_studio_code:*:*",
|
||||
},
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "ms-vsliveshare.vsliveshare",
|
||||
Source: "vscode_extensions",
|
||||
Version: "1.0.5918",
|
||||
Vendor: "Microsoft",
|
||||
},
|
||||
cpe: "cpe:2.3:a:microsoft:visual_studio_live_share:1.0.5918:*:*:*:*:visual_studio_code:*:*",
|
||||
},
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "dbaeumer.vscode-eslint",
|
||||
Source: "vscode_extensions",
|
||||
Version: "2.4.4",
|
||||
Vendor: "Microsoft",
|
||||
},
|
||||
cpe: "cpe:2.3:a:microsoft:visual_studio_code_eslint_extension:2.4.4:*:*:*:*:visual_studio_code:*:*",
|
||||
},
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "vscjava.vscode-maven",
|
||||
Source: "vscode_extensions",
|
||||
Version: "0.44.0",
|
||||
Vendor: "Microsoft",
|
||||
},
|
||||
cpe: "cpe:2.3:a:microsoft:vscode-maven:0.44.0:*:*:*:*:visual_studio_code:*:*",
|
||||
},
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "ms-vscode.powershell",
|
||||
Source: "vscode_extensions",
|
||||
Version: "2024.0.0",
|
||||
Vendor: "Microsoft",
|
||||
},
|
||||
cpe: "cpe:2.3:a:microsoft:powershell_extension:2024.0.0:*:*:*:*:visual_studio_code:*:*",
|
||||
},
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "ms-vscode-remote.vscode-remote-extensionpack",
|
||||
Source: "vscode_extensions",
|
||||
Version: "0.25.0",
|
||||
Vendor: "Microsoft",
|
||||
},
|
||||
cpe: "cpe:2.3:a:microsoft:remote_development:0.25.0:*:*:*:*:visual_studio_code:*:*",
|
||||
},
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "vknabel.vscode-swiftlint",
|
||||
Source: "vscode_extensions",
|
||||
Version: "1.8.3",
|
||||
Vendor: "vknabel",
|
||||
},
|
||||
cpe: "cpe:2.3:a:swiftlint_project:swiftlint:1.8.3:*:*:*:*:visual_studio_code:*:*",
|
||||
},
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "vknabel.vscode-swiftformat",
|
||||
Source: "vscode_extensions",
|
||||
Version: "1.6.7",
|
||||
Vendor: "vknabel",
|
||||
},
|
||||
cpe: "cpe:2.3:a:swiftformat_project:swiftformat:1.6.7:*:*:*:*:visual_studio_code:*:*",
|
||||
},
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "jbenden.c-cpp-flylint",
|
||||
Source: "vscode_extensions",
|
||||
Version: "1.14.0",
|
||||
Vendor: "Joseph Benden",
|
||||
},
|
||||
cpe: `cpe:2.3:a:c\/c\+\+_advanced_lint_project:c\/c\+\+_advanced_lint:1.14.0:*:*:*:*:visual_studio_code:*:*`,
|
||||
},
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "stripe.vscode-stripe",
|
||||
Source: "vscode_extensions",
|
||||
Version: "2.0.14",
|
||||
Vendor: "Stripe",
|
||||
},
|
||||
cpe: `cpe:2.3:a:stripe:stripe:2.0.14:*:*:*:*:visual_studio_code:*:*`,
|
||||
},
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "vscodevim.vim",
|
||||
Source: "vscode_extensions",
|
||||
Version: "1.27.2",
|
||||
Vendor: "vscodevim",
|
||||
},
|
||||
cpe: `cpe:2.3:a:vim_project:vim:1.27.2:*:*:*:*:visual_studio_code:*:*`,
|
||||
},
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "svelte.svelte-vscode",
|
||||
Source: "vscode_extensions",
|
||||
Version: "108.3.1",
|
||||
Vendor: "Svelte",
|
||||
},
|
||||
cpe: `cpe:2.3:a:svelte:svelte:108.3.1:*:*:*:*:visual_studio_code:*:*`,
|
||||
},
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "lextudio.restructuredtext",
|
||||
Source: "vscode_extensions",
|
||||
Version: "189.3.0",
|
||||
Vendor: "LeXtudio Inc.",
|
||||
},
|
||||
cpe: `cpe:2.3:a:lextudio:restructuredtext:189.3.0:*:*:*:*:visual_studio_code:*:*`,
|
||||
},
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "ms-vscode-remote.remote-containers",
|
||||
Source: "vscode_extensions",
|
||||
Version: "0.348.0",
|
||||
Vendor: "Microsoft",
|
||||
},
|
||||
cpe: `cpe:2.3:a:microsoft:remote:0.348.0:*:*:*:*:visual_studio_code:*:*`,
|
||||
},
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "ms-kubernetes-tools.vscode-kubernetes-tools",
|
||||
Source: "vscode_extensions",
|
||||
Version: "0.348.0",
|
||||
Vendor: "Microsoft",
|
||||
},
|
||||
cpe: `cpe:2.3:a:microsoft:kubernetes_tools:0.348.0:*:*:*:*:visual_studio_code:*:*`,
|
||||
},
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "ms-dotnettools.vscode-dotnet-sdk",
|
||||
Source: "vscode_extensions",
|
||||
Version: "0.8.0",
|
||||
Vendor: "Microsoft",
|
||||
},
|
||||
cpe: `cpe:2.3:a:microsoft:.net_education_bundle_sdk_install_tool:0.8.0:*:*:*:*:visual_studio_code:*:*`,
|
||||
},
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "ms-dotnettools.vscode-dotnet-runtime",
|
||||
Source: "vscode_extensions",
|
||||
Version: "2.0.2",
|
||||
Vendor: "Microsoft",
|
||||
},
|
||||
cpe: `cpe:2.3:a:microsoft:.net_install_tool_for_extension_authors:2.0.2:*:*:*:*:visual_studio_code:*:*`,
|
||||
},
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "ms-vscode-remote.remote-wsl",
|
||||
Source: "vscode_extensions",
|
||||
Version: "0.86.0",
|
||||
Vendor: "Microsoft",
|
||||
},
|
||||
cpe: `cpe:2.3:a:microsoft:windows_subsystem_for_linux:0.86.0:*:*:*:*:visual_studio_code:*:*`,
|
||||
},
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "mongodb.mongodb-vscode",
|
||||
Source: "vscode_extensions",
|
||||
Version: "1.5.0",
|
||||
Vendor: "MongoDB",
|
||||
},
|
||||
cpe: `cpe:2.3:a:mongodb:mongodb:1.5.0:*:*:*:*:visual_studio_code:*:*`,
|
||||
},
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "oracle.mysql-shell-for-vs-code",
|
||||
Source: "vscode_extensions",
|
||||
Version: "1.14.2",
|
||||
Vendor: "MongoDB",
|
||||
},
|
||||
cpe: `cpe:2.3:a:oracle:mysql_shell:1.14.2:*:*:*:*:visual_studio_code:*:*`,
|
||||
},
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "snyk-security.snyk-vulnerability-scanner",
|
||||
Source: "vscode_extensions",
|
||||
Version: "2.3.6",
|
||||
Vendor: "Snyk",
|
||||
},
|
||||
cpe: `cpe:2.3:a:snyk:snyk_security:2.3.6:*:*:*:*:visual_studio_code:*:*`,
|
||||
},
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "sourcegraph.cody-ai",
|
||||
Source: "vscode_extensions",
|
||||
Version: "1.8.0",
|
||||
Vendor: "Sourcegraph",
|
||||
},
|
||||
cpe: `cpe:2.3:a:sourcegraph:cody:1.8.0:*:*:*:*:visual_studio_code:*:*`,
|
||||
},
|
||||
// There are vulnerabilities for `cpe:2.3:a:redhat:vscode-xml:` in
|
||||
// NVD's database but there's no entry for `cpe:2.3:a:redhat:vscode-xml:0.26.1`
|
||||
// in NVD's CPE database.
|
||||
/*
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "redhat.vscode-xml",
|
||||
Source: "vscode_extensions",
|
||||
Version: "0.26.1",
|
||||
Vendor: "Red Hat",
|
||||
},
|
||||
cpe: `cpe:2.3:a:redhat:vscode-xml:0.26.1:*:*:*:*:visual_studio_code:*:*`,
|
||||
},
|
||||
*/
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "github.vscode-pull-request-github",
|
||||
Source: "vscode_extensions",
|
||||
Version: "0.82.0",
|
||||
Vendor: "GitHub",
|
||||
},
|
||||
cpe: `cpe:2.3:a:github:pull_requests_and_issues:0.82.0:*:*:*:*:visual_studio_code:*:*`,
|
||||
},
|
||||
{
|
||||
software: fleet.Software{
|
||||
Name: "Google Chrome Helper.app",
|
||||
@ -1369,18 +1606,22 @@ func TestCPEFromSoftwareIntegration(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
tempDir := t.TempDir()
|
||||
// NVD_TEST_CPEDB_PATH can be used to speed up development (sync cpe.sqlite only once).
|
||||
dbPath := os.Getenv("NVD_TEST_CPEDB_PATH")
|
||||
if dbPath == "" {
|
||||
nettest.Run(t)
|
||||
tempDir := t.TempDir()
|
||||
err := DownloadCPEDBFromGithub(tempDir, "")
|
||||
require.NoError(t, err)
|
||||
dbPath = filepath.Join(tempDir, "cpe.sqlite")
|
||||
} else {
|
||||
require.FileExists(t, dbPath)
|
||||
t.Logf("Using %s as database file", dbPath)
|
||||
}
|
||||
|
||||
err := DownloadCPEDBFromGithub(tempDir, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
dbPath := filepath.Join(tempDir, "cpe.sqlite")
|
||||
db, err := sqliteDB(dbPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = DownloadCPETranslationsFromGithub(tempDir, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
cpeTranslationsPath := filepath.Join(".", cpeTranslationsFilename)
|
||||
cpeTranslations, err := loadCPETranslations(cpeTranslationsPath)
|
||||
require.NoError(t, err)
|
||||
|
@ -140,6 +140,216 @@
|
||||
"vendor": ["jetbrains"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"software": {
|
||||
"name": ["ms-python.python"],
|
||||
"source": ["vscode_extensions"]
|
||||
},
|
||||
"filter": {
|
||||
"product": ["python_extension"],
|
||||
"vendor": ["microsoft"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"software": {
|
||||
"name": ["ms-vsliveshare.vsliveshare"],
|
||||
"source": ["vscode_extensions"]
|
||||
},
|
||||
"filter": {
|
||||
"product": ["visual_studio_live_share"],
|
||||
"vendor": ["microsoft"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"software": {
|
||||
"name": ["dbaeumer.vscode-eslint"],
|
||||
"source": ["vscode_extensions"]
|
||||
},
|
||||
"filter": {
|
||||
"product": ["visual_studio_code_eslint_extension"],
|
||||
"vendor": ["microsoft"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"software": {
|
||||
"name": ["ms-vscode.powershell"],
|
||||
"source": ["vscode_extensions"]
|
||||
},
|
||||
"filter": {
|
||||
"product": ["powershell_extension"],
|
||||
"vendor": ["microsoft"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"software": {
|
||||
"name": ["ms-vscode-remote.vscode-remote-extensionpack"],
|
||||
"source": ["vscode_extensions"]
|
||||
},
|
||||
"filter": {
|
||||
"product": ["remote_development"],
|
||||
"vendor": ["microsoft"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"software": {
|
||||
"name": ["vknabel.vscode-swiftlint"],
|
||||
"source": ["vscode_extensions"]
|
||||
},
|
||||
"filter": {
|
||||
"product": ["swiftlint"],
|
||||
"vendor": ["swiftlint_project"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"software": {
|
||||
"name": ["vknabel.vscode-swiftformat"],
|
||||
"source": ["vscode_extensions"]
|
||||
},
|
||||
"filter": {
|
||||
"product": ["swiftformat"],
|
||||
"vendor": ["swiftformat_project"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"software": {
|
||||
"name": ["jbenden.c-cpp-flylint"],
|
||||
"source": ["vscode_extensions"]
|
||||
},
|
||||
"filter": {
|
||||
"product": ["c\\/c\\+\\+_advanced_lint"],
|
||||
"vendor": ["c\\/c\\+\\+_advanced_lint_project"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"software": {
|
||||
"name": ["stripe.vscode-stripe"],
|
||||
"source": ["vscode_extensions"]
|
||||
},
|
||||
"filter": {
|
||||
"product": ["stripe"],
|
||||
"vendor": ["stripe"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"software": {
|
||||
"name": ["vscodevim.vim"],
|
||||
"source": ["vscode_extensions"]
|
||||
},
|
||||
"filter": {
|
||||
"product": ["vim"],
|
||||
"vendor": ["vim_project"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"software": {
|
||||
"name": ["svelte.svelte-vscode"],
|
||||
"source": ["vscode_extensions"]
|
||||
},
|
||||
"filter": {
|
||||
"product": ["svelte"],
|
||||
"vendor": ["svelte"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"software": {
|
||||
"name": ["ms-vscode-remote.remote-containers"],
|
||||
"source": ["vscode_extensions"]
|
||||
},
|
||||
"filter": {
|
||||
"product": ["remote"],
|
||||
"vendor": ["microsoft"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"software": {
|
||||
"name": ["ms-kubernetes-tools.vscode-kubernetes-tools"],
|
||||
"source": ["vscode_extensions"]
|
||||
},
|
||||
"filter": {
|
||||
"product": ["kubernetes_tools"],
|
||||
"vendor": ["microsoft"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"software": {
|
||||
"name": ["ms-dotnettools.vscode-dotnet-sdk"],
|
||||
"source": ["vscode_extensions"]
|
||||
},
|
||||
"filter": {
|
||||
"product": [".net_education_bundle_sdk_install_tool"],
|
||||
"vendor": ["microsoft"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"software": {
|
||||
"name": ["ms-dotnettools.vscode-dotnet-runtime"],
|
||||
"source": ["vscode_extensions"]
|
||||
},
|
||||
"filter": {
|
||||
"product": [".net_install_tool_for_extension_authors"],
|
||||
"vendor": ["microsoft"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"software": {
|
||||
"name": ["ms-vscode-remote.remote-wsl"],
|
||||
"source": ["vscode_extensions"]
|
||||
},
|
||||
"filter": {
|
||||
"product": ["windows_subsystem_for_linux"],
|
||||
"vendor": ["microsoft"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"software": {
|
||||
"name": ["mongodb.mongodb-vscode"],
|
||||
"source": ["vscode_extensions"]
|
||||
},
|
||||
"filter": {
|
||||
"product": ["mongodb"],
|
||||
"vendor": ["mongodb"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"software": {
|
||||
"name": ["oracle.mysql-shell-for-vs-code"],
|
||||
"source": ["vscode_extensions"]
|
||||
},
|
||||
"filter": {
|
||||
"product": ["mysql_shell"],
|
||||
"vendor": ["oracle"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"software": {
|
||||
"name": ["snyk-security.snyk-vulnerability-scanner"],
|
||||
"source": ["vscode_extensions"]
|
||||
},
|
||||
"filter": {
|
||||
"product": ["snyk_security"],
|
||||
"vendor": ["snyk"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"software": {
|
||||
"name": ["sourcegraph.cody-ai"],
|
||||
"source": ["vscode_extensions"]
|
||||
},
|
||||
"filter": {
|
||||
"product": ["cody"],
|
||||
"vendor": ["sourcegraph"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"software": {
|
||||
"name": ["github.vscode-pull-request-github"],
|
||||
"source": ["vscode_extensions"]
|
||||
},
|
||||
"filter": {
|
||||
"product": ["pull_requests_and_issues"],
|
||||
"vendor": ["github"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"software": {
|
||||
"name": ["Microsoft Edge"],
|
||||
|
@ -404,66 +404,69 @@ func checkCVEs(
|
||||
// No such vendor in the Vulnerability dictionary
|
||||
continue
|
||||
}
|
||||
cacheHits := cache.Get([]*wfn.Attributes{CPEItem.GetMeta()})
|
||||
for _, matches := range cacheHits {
|
||||
if len(matches.CPEs) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if rule, ok := knownNVDBugRules.FindMatch(
|
||||
matches.CVE.ID(),
|
||||
); ok {
|
||||
if !rule.CPEMatches(CPEItem.GetMeta()) {
|
||||
cpeItemsWithAliases := expandCPEAliases(CPEItem.GetMeta())
|
||||
for _, cpeItem := range cpeItemsWithAliases {
|
||||
cacheHits := cache.Get([]*wfn.Attributes{cpeItem})
|
||||
for _, matches := range cacheHits {
|
||||
if len(matches.CPEs) == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// For chrome/firefox extensions we only want to match vulnerabilities
|
||||
// that are reported explicitly for target_sw == "chrome" or target_sw = "firefox".
|
||||
//
|
||||
// Why? In many ocassions the NVD dataset reports vulnerabilities in client applications
|
||||
// with target_sw == "*", meaning the client application is vulnerable on all operating systems.
|
||||
// Such rules we want to ignore here to prevent many false positives that do not apply to the
|
||||
// Chrome or Firefox environment.
|
||||
if CPEItem.GetMeta().TargetSW == "chrome" || CPEItem.GetMeta().TargetSW == "firefox" {
|
||||
if !matchesExactTargetSW(
|
||||
CPEItem.GetMeta().TargetSW,
|
||||
[]string{"chrome", "firefox"},
|
||||
matches.CVE.Config(),
|
||||
) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
resolvedVersion, err := getMatchingVersionEndExcluding(ctx, matches.CVE.ID(), CPEItem.GetMeta(), dict, logger)
|
||||
if err != nil {
|
||||
level.Debug(logger).Log("err", err)
|
||||
}
|
||||
|
||||
if _, ok := CPEItem.(softwareCPEWithNVDMeta); ok {
|
||||
|
||||
vuln := fleet.SoftwareVulnerability{
|
||||
SoftwareID: CPEItem.GetID(),
|
||||
CVE: matches.CVE.ID(),
|
||||
ResolvedInVersion: ptr.String(resolvedVersion),
|
||||
if rule, ok := knownNVDBugRules.FindMatch(
|
||||
matches.CVE.ID(),
|
||||
); ok {
|
||||
if !rule.CPEMatches(cpeItem) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
softwareMu.Lock()
|
||||
foundSoftwareVulns = append(foundSoftwareVulns, vuln)
|
||||
softwareMu.Unlock()
|
||||
} else if _, ok := CPEItem.(osCPEWithNVDMeta); ok {
|
||||
|
||||
vuln := fleet.OSVulnerability{
|
||||
OSID: CPEItem.GetID(),
|
||||
CVE: matches.CVE.ID(),
|
||||
ResolvedInVersion: ptr.String(resolvedVersion),
|
||||
// For chrome/firefox extensions we only want to match vulnerabilities
|
||||
// that are reported explicitly for target_sw == "chrome" or target_sw = "firefox".
|
||||
//
|
||||
// Why? In many occasions the NVD dataset reports vulnerabilities in client applications
|
||||
// with target_sw == "*", meaning the client application is vulnerable on all operating systems.
|
||||
// Such rules we want to ignore here to prevent many false positives that do not apply to the
|
||||
// Chrome or Firefox environment.
|
||||
if cpeItem.TargetSW == "chrome" || cpeItem.TargetSW == "firefox" {
|
||||
if !matchesExactTargetSW(
|
||||
cpeItem.TargetSW,
|
||||
[]string{"chrome", "firefox"},
|
||||
matches.CVE.Config(),
|
||||
) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
osMu.Lock()
|
||||
foundOSVulns = append(foundOSVulns, vuln)
|
||||
osMu.Unlock()
|
||||
}
|
||||
resolvedVersion, err := getMatchingVersionEndExcluding(ctx, matches.CVE.ID(), cpeItem, dict, logger)
|
||||
if err != nil {
|
||||
level.Debug(logger).Log("err", err)
|
||||
}
|
||||
|
||||
if _, ok := CPEItem.(softwareCPEWithNVDMeta); ok {
|
||||
vuln := fleet.SoftwareVulnerability{
|
||||
SoftwareID: CPEItem.GetID(),
|
||||
CVE: matches.CVE.ID(),
|
||||
ResolvedInVersion: ptr.String(resolvedVersion),
|
||||
}
|
||||
|
||||
softwareMu.Lock()
|
||||
foundSoftwareVulns = append(foundSoftwareVulns, vuln)
|
||||
softwareMu.Unlock()
|
||||
} else if _, ok := CPEItem.(osCPEWithNVDMeta); ok {
|
||||
|
||||
vuln := fleet.OSVulnerability{
|
||||
OSID: CPEItem.GetID(),
|
||||
CVE: matches.CVE.ID(),
|
||||
ResolvedInVersion: ptr.String(resolvedVersion),
|
||||
}
|
||||
|
||||
osMu.Lock()
|
||||
foundOSVulns = append(foundOSVulns, vuln)
|
||||
osMu.Unlock()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
case <-ctx.Done():
|
||||
level.Debug(logger).Log("msg", "quitting")
|
||||
@ -485,6 +488,45 @@ func checkCVEs(
|
||||
return foundSoftwareVulns, foundOSVulns, nil
|
||||
}
|
||||
|
||||
// expandCPEAliases will generate new *wfn.Attributes from the given cpeItem.
|
||||
// It returns a slice with the given cpeItem plus the generated *wfn.Attributes.
|
||||
//
|
||||
// We need this because entries in the CPE database are not consistent.
|
||||
// E.g. some Visual Studio Code extensions are defined with target_sw=visual_studio_code
|
||||
// and others are defined with target_sw=visual_studio.
|
||||
// E.g. The python extension for Visual Studio Code is defined with
|
||||
// product=python_extension,target_sw=visual_studio_code and with
|
||||
// product=visual_studio_code,target_sw=python.
|
||||
func expandCPEAliases(cpeItem *wfn.Attributes) []*wfn.Attributes {
|
||||
cpeItems := []*wfn.Attributes{cpeItem}
|
||||
|
||||
// Some VSCode extensions are defined with target_sw=visual_studio_code
|
||||
// and others are defined with target_sw=visual_studio.
|
||||
for _, cpeItem := range cpeItems {
|
||||
if cpeItem.TargetSW == "visual_studio_code" {
|
||||
cpeItem2 := *cpeItem
|
||||
cpeItem2.TargetSW = "visual_studio"
|
||||
cpeItems = append(cpeItems, &cpeItem2)
|
||||
}
|
||||
}
|
||||
|
||||
// The python extension is defined in two ways in the CPE database:
|
||||
// cpe:2.3:a:microsoft:python_extension:2024.2.1:*:*:*:*:visual_studio_code:*:*
|
||||
// cpe:2.3:a:microsoft:visual_studio_code:2024.2.1:*:*:*:*:python:*:*
|
||||
for _, cpeItem := range cpeItems {
|
||||
if cpeItem.TargetSW == "visual_studio_code" &&
|
||||
cpeItem.Vendor == "microsoft" &&
|
||||
cpeItem.Product == "python_extension" {
|
||||
cpeItem2 := *cpeItem
|
||||
cpeItem2.Product = "visual_studio_code"
|
||||
cpeItem2.TargetSW = "python"
|
||||
cpeItems = append(cpeItems, &cpeItem2)
|
||||
}
|
||||
}
|
||||
|
||||
return cpeItems
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
@ -130,20 +131,26 @@ func (d *threadSafeDSMock) InsertSoftwareVulnerability(ctx context.Context, vuln
|
||||
}
|
||||
|
||||
func TestTranslateCPEToCVE(t *testing.T) {
|
||||
nettest.Run(t)
|
||||
|
||||
tempDir := t.TempDir()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// download the CVEs once for all sub-tests, and then disable syncing
|
||||
err := nettest.RunWithNetRetry(t, func() error {
|
||||
// We use cveFeedPrefixURL="https://nvd.nist.gov/feeds/json/cve/1.1/" because a full sync
|
||||
// with the NVD API 2.0 takes a long time (>15m). These feeds will be deprecated
|
||||
// on December 15th and this test will start failing then.
|
||||
return DownloadNVDCVEFeed(tempDir, "https://nvd.nist.gov/feeds/json/cve/1.1/", false, log.NewNopLogger())
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// NVD_TEST_VULNDB_DIR can be used to speed up development (sync vulnerability data only once).
|
||||
tempDir := os.Getenv("NVD_TEST_VULNDB_DIR")
|
||||
if tempDir == "" {
|
||||
nettest.Run(t)
|
||||
// download the CVEs once for all sub-tests, and then disable syncing
|
||||
tempDir = t.TempDir()
|
||||
err := nettest.RunWithNetRetry(t, func() error {
|
||||
// We use cveFeedPrefixURL="https://nvd.nist.gov/feeds/json/cve/1.1/" because a full sync
|
||||
// with the NVD API 2.0 takes a long time (>15m). These feeds will be deprecated
|
||||
// TBD during 2024 and this test will start failing then.
|
||||
// For more information see: https://nvd.nist.gov/general/news/change-timeline.
|
||||
return DownloadNVDCVEFeed(tempDir, "https://nvd.nist.gov/feeds/json/cve/1.1/", false, log.NewNopLogger())
|
||||
})
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.DirExists(t, tempDir)
|
||||
t.Logf("Using %s as database path", tempDir)
|
||||
}
|
||||
|
||||
cveTests := map[string]struct {
|
||||
cpe string
|
||||
@ -261,6 +268,48 @@ func TestTranslateCPEToCVE(t *testing.T) {
|
||||
},
|
||||
continuesToUpdate: true,
|
||||
},
|
||||
"cpe:2.3:a:microsoft:windows_subsystem_for_linux:0.63.10:*:*:*:*:visual_studio_code:*:*": {
|
||||
includedCVEs: []cve{
|
||||
{ID: "CVE-2021-43907", resolvedInVersion: "0.63.11"},
|
||||
},
|
||||
continuesToUpdate: false,
|
||||
},
|
||||
"cpe:2.3:a:github:pull_requests_and_issues:0.66.1:*:*:*:*:visual_studio_code:*:*": {
|
||||
includedCVEs: []cve{
|
||||
{ID: "CVE-2023-36867", resolvedInVersion: "0.66.2"},
|
||||
},
|
||||
continuesToUpdate: false,
|
||||
},
|
||||
"cpe:2.3:a:microsoft:python_extension:2020.9.1:*:*:*:*:visual_studio_code:*:*": {
|
||||
includedCVEs: []cve{
|
||||
{ID: "CVE-2020-17163", resolvedInVersion: "2020.9.2"},
|
||||
},
|
||||
continuesToUpdate: false,
|
||||
},
|
||||
"cpe:2.3:a:microsoft:jupyter:2023.10.10:*:*:*:*:visual_studio_code:*:*": {
|
||||
includedCVEs: []cve{
|
||||
{ID: "CVE-2023-36018", resolvedInVersion: "2023.10.1100000000"},
|
||||
},
|
||||
continuesToUpdate: false,
|
||||
},
|
||||
"cpe:2.3:a:microsoft:jupyter:2024.2.0:*:*:*:*:visual_studio_code:*:*": {
|
||||
includedCVEs: []cve{},
|
||||
continuesToUpdate: false,
|
||||
},
|
||||
"cpe:2.3:a:microsoft:visual_studio_code_eslint_extension:2.0.0:*:*:*:*:visual_studio_code:*:*": {
|
||||
includedCVEs: []cve{
|
||||
{ID: "CVE-2020-1481", resolvedInVersion: "2.1.7"},
|
||||
},
|
||||
continuesToUpdate: false,
|
||||
},
|
||||
"cpe:2.3:a:microsoft:python_extension:2020.4.0:*:*:*:*:visual_studio_code:*:*": {
|
||||
includedCVEs: []cve{
|
||||
{ID: "CVE-2020-1171", resolvedInVersion: "2020.5.0"},
|
||||
{ID: "CVE-2020-1192", resolvedInVersion: "2020.5.0"},
|
||||
{ID: "CVE-2020-17163", resolvedInVersion: "2020.9.2"},
|
||||
},
|
||||
continuesToUpdate: false,
|
||||
},
|
||||
}
|
||||
|
||||
cveOSTests := []struct {
|
||||
@ -701,3 +750,70 @@ func loadDict(t *testing.T, path string) cvefeed.Dictionary {
|
||||
}
|
||||
return dict
|
||||
}
|
||||
|
||||
func TestExpandCPEAliases(t *testing.T) {
|
||||
firefox := &wfn.Attributes{
|
||||
Vendor: "mozilla",
|
||||
Product: "firefox",
|
||||
Version: "93.0.100",
|
||||
}
|
||||
chromePlugin := &wfn.Attributes{
|
||||
Vendor: "google",
|
||||
Product: "plugin foobar",
|
||||
Version: "93.0.100",
|
||||
TargetSW: "chrome",
|
||||
}
|
||||
|
||||
vsCodeExtension := &wfn.Attributes{
|
||||
Vendor: "Microsoft",
|
||||
Product: "foo.extension",
|
||||
Version: "2024.2.1",
|
||||
TargetSW: "visual_studio_code",
|
||||
}
|
||||
vsCodeExtensionAlias := *vsCodeExtension
|
||||
vsCodeExtensionAlias.TargetSW = "visual_studio"
|
||||
|
||||
pythonCodeExtension := &wfn.Attributes{
|
||||
Vendor: "microsoft",
|
||||
Product: "python_extension",
|
||||
Version: "2024.2.1",
|
||||
TargetSW: "visual_studio_code",
|
||||
}
|
||||
pythonCodeExtensionAlias1 := *pythonCodeExtension
|
||||
pythonCodeExtensionAlias1.TargetSW = "visual_studio"
|
||||
pythonCodeExtensionAlias2 := *pythonCodeExtension
|
||||
pythonCodeExtensionAlias2.Product = "visual_studio_code"
|
||||
pythonCodeExtensionAlias2.TargetSW = "python"
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
cpeItem *wfn.Attributes
|
||||
expectedAliases []*wfn.Attributes
|
||||
}{
|
||||
{
|
||||
name: "no expansion without target_sw",
|
||||
cpeItem: firefox,
|
||||
expectedAliases: []*wfn.Attributes{firefox},
|
||||
},
|
||||
{
|
||||
name: "no expansion with target_sw",
|
||||
cpeItem: chromePlugin,
|
||||
expectedAliases: []*wfn.Attributes{chromePlugin},
|
||||
},
|
||||
{
|
||||
name: "visual studio code extension",
|
||||
cpeItem: vsCodeExtension,
|
||||
expectedAliases: []*wfn.Attributes{vsCodeExtension, &vsCodeExtensionAlias},
|
||||
},
|
||||
{
|
||||
name: "python visual studio code extension",
|
||||
cpeItem: pythonCodeExtension,
|
||||
expectedAliases: []*wfn.Attributes{pythonCodeExtension, &pythonCodeExtensionAlias1, &pythonCodeExtensionAlias2},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
aliases := expandCPEAliases(tc.cpeItem)
|
||||
require.Equal(t, tc.expectedAliases, aliases)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -145,6 +145,14 @@ func productVariations(s *fleet.Software) []string {
|
||||
r = append(r, re)
|
||||
}
|
||||
|
||||
// VSCode extensions have a unique s.Name of the form "<vendor>.<extension>" (aka extension ID)
|
||||
if s.Source == "vscode_extensions" {
|
||||
parts := strings.SplitN(s.Name, ".", 2)
|
||||
if len(parts) == 2 && parts[1] != "" {
|
||||
r = append(r, parts[1])
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
@ -178,6 +186,14 @@ func vendorVariations(s *fleet.Software) []string {
|
||||
}
|
||||
}
|
||||
|
||||
// VSCode extensions have a unique s.Name of the form "<vendor>.<extension>" (aka extension ID)
|
||||
if s.Source == "vscode_extensions" {
|
||||
parts := strings.SplitN(s.Name, ".", 2)
|
||||
if len(parts) == 2 && parts[0] != "" {
|
||||
r = append(r, parts[0])
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
@ -211,6 +227,8 @@ func targetSW(s *fleet.Software) string {
|
||||
return `node.js`
|
||||
case "programs":
|
||||
return "windows"
|
||||
case "vscode_extensions":
|
||||
return "visual_studio_code"
|
||||
}
|
||||
return "*"
|
||||
}
|
||||
|
@ -157,6 +157,11 @@ func TestVariations(t *testing.T) {
|
||||
vendorVariations: []string{"apple", "python3"},
|
||||
productVariations: []string{"python"},
|
||||
},
|
||||
{
|
||||
software: fleet.Software{Name: "ms-python.python", Version: "3.8.9", BundleIdentifier: "", Source: "vscode_extensions", Vendor: "Microsoft"},
|
||||
vendorVariations: []string{"microsoft", "ms-python"},
|
||||
productVariations: []string{"python", "ms-python.python"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range variationsTestCases {
|
||||
|
Loading…
Reference in New Issue
Block a user