mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #37323 # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. ## Testing - [x] Added/updated automated tests - [x] QA'd all new/changed functionality manually <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved JetBrains software version detection to support the newer two-part version format (e.g., WebStorm 2025.1). * Enhanced CVE/vulnerability tracking accuracy for JetBrains products with updated version number parsing. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
3716 lines
158 KiB
Go
3716 lines
158 KiB
Go
package osquery_utils
|
||
|
||
import (
|
||
"bufio"
|
||
"bytes"
|
||
"context"
|
||
"encoding/base64"
|
||
"encoding/hex"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"log/slog"
|
||
"slices"
|
||
"sort"
|
||
"strconv"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/fleetdm/fleet/v4/pkg/optjson"
|
||
microsoft_mdm "github.com/fleetdm/fleet/v4/server/mdm/microsoft"
|
||
|
||
"github.com/WatchBeam/clock"
|
||
"github.com/fleetdm/fleet/v4/server/config"
|
||
"github.com/fleetdm/fleet/v4/server/contexts/publicip"
|
||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||
apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple"
|
||
"github.com/fleetdm/fleet/v4/server/mock"
|
||
common_mysql "github.com/fleetdm/fleet/v4/server/platform/mysql"
|
||
"github.com/fleetdm/fleet/v4/server/ptr"
|
||
"github.com/fleetdm/fleet/v4/server/service/async"
|
||
"github.com/stretchr/testify/assert"
|
||
"github.com/stretchr/testify/require"
|
||
"golang.org/x/exp/maps"
|
||
)
|
||
|
||
func TestSoftwareIngestionMutations(t *testing.T) {
|
||
dcvViewer := &fleet.Software{
|
||
BundleIdentifier: "com.nicesoftware.dcvviewer",
|
||
Source: "apps",
|
||
Version: "2024.0 (r8004)",
|
||
}
|
||
|
||
MutateSoftwareOnIngestion(t.Context(), dcvViewer, slog.New(slog.DiscardHandler))
|
||
assert.Equal(t, "2024.0.8004", dcvViewer.Version)
|
||
|
||
noOp := &fleet.Software{
|
||
BundleIdentifier: "com.nicesoftware.dcvviewer",
|
||
Source: "apps",
|
||
Version: "2024",
|
||
}
|
||
|
||
MutateSoftwareOnIngestion(t.Context(), noOp, slog.New(slog.DiscardHandler))
|
||
assert.Equal(t, "2024", noOp.Version)
|
||
|
||
noMatch := &fleet.Software{
|
||
BundleIdentifier: "com.google.chrome",
|
||
Source: "apps",
|
||
Version: "2024.0 (r8004)",
|
||
}
|
||
|
||
MutateSoftwareOnIngestion(t.Context(), noMatch, slog.New(slog.DiscardHandler))
|
||
assert.Equal(t, "2024.0 (r8004)", noMatch.Version)
|
||
|
||
for expectedName, s := range map[string]*fleet.Software{
|
||
"SynologyAssistant": {
|
||
Name: "DSAssistant",
|
||
BundleIdentifier: "com.synology.DSAssistant",
|
||
Source: "apps",
|
||
},
|
||
"BlueStacksMIM": {
|
||
Name: "HD-MultiInstanceManager",
|
||
BundleIdentifier: "com.now.gg.BlueStacksMIM",
|
||
Source: "apps",
|
||
},
|
||
"JPKIUninstall": {
|
||
Name: "JPKIUninstall.scpt",
|
||
BundleIdentifier: "jp.go.jpki.JPKIUninstall",
|
||
Source: "apps",
|
||
},
|
||
"OracleDataModeler": {
|
||
Name: "datamodeler.sh",
|
||
BundleIdentifier: "com.oracle.OracleDataModeler",
|
||
Source: "apps",
|
||
},
|
||
"EaseUS NTFS Service": {
|
||
Name: "euntfsservice",
|
||
BundleIdentifier: "com.easeus.ntfsformacdaemon",
|
||
Source: "apps",
|
||
},
|
||
"Poly Lens Desktop (Legacy)": {
|
||
Name: "legacyhost",
|
||
BundleIdentifier: "com.poly.lens.legacyhost.app",
|
||
Source: "apps",
|
||
},
|
||
"Zen Browser Plugin Container": {
|
||
Name: "plugin-container",
|
||
BundleIdentifier: "app.zen-browser.plugincontainer",
|
||
Source: "apps",
|
||
},
|
||
"Mozilla Plugin Container": {
|
||
Name: "plugin-container",
|
||
BundleIdentifier: "org.mozilla.plugincontainer",
|
||
Source: "apps",
|
||
},
|
||
"Chrome Remote Desktop Host Uninstaller": {
|
||
Name: "remoting_host_uninstaller",
|
||
BundleIdentifier: "com.google.chromeremotedesktop.me2me-host-uninstaller",
|
||
Source: "apps",
|
||
},
|
||
"Android Emulator": {
|
||
Name: "runemu",
|
||
BundleIdentifier: "",
|
||
Source: "apps",
|
||
},
|
||
"Oracle SQLDeveloper": {
|
||
Name: "sqldeveloper.sh/",
|
||
BundleIdentifier: "com.oracle.SQLDeveloper",
|
||
Source: "apps",
|
||
},
|
||
} {
|
||
MutateSoftwareOnIngestion(t.Context(), s, slog.New(slog.DiscardHandler))
|
||
assert.Equal(t, expectedName, s.Name)
|
||
}
|
||
|
||
// Test customAppSanitizers TNMS case
|
||
sw := &fleet.Software{
|
||
Name: "TNMS 21.10.0.590.1",
|
||
BundleIdentifier: "TNMS_21.10.0.590.1",
|
||
Source: "apps",
|
||
}
|
||
MutateSoftwareOnIngestion(t.Context(), sw, slog.New(slog.DiscardHandler))
|
||
assert.Equal(t, "TNMS", sw.Name)
|
||
assert.Equal(t, "21.10.0.590.1", sw.Version)
|
||
|
||
// Test JetBrains version sanitizer - extracts version from product name
|
||
jetbrainsGoLand := &fleet.Software{
|
||
Name: "GoLand 2025.3.3",
|
||
Source: "programs",
|
||
Vendor: "JetBrains s.r.o.",
|
||
Version: "253.31033.139",
|
||
}
|
||
MutateSoftwareOnIngestion(t.Context(), jetbrainsGoLand, slog.New(slog.DiscardHandler))
|
||
assert.Equal(t, "2025.3.3", jetbrainsGoLand.Version)
|
||
|
||
// Test JetBrains with 2-part year.minor version (like "WebStorm 2025.1")
|
||
jetbrainsWebStorm := &fleet.Software{
|
||
Name: "WebStorm 2025.1",
|
||
Source: "programs",
|
||
Vendor: "JetBrains s.r.o.",
|
||
Version: "251.23774.424",
|
||
}
|
||
MutateSoftwareOnIngestion(t.Context(), jetbrainsWebStorm, slog.New(slog.DiscardHandler))
|
||
assert.Equal(t, "2025.1", jetbrainsWebStorm.Version)
|
||
|
||
// Test JetBrains with 4-part version number
|
||
jetbrainsIntelliJ := &fleet.Software{
|
||
Name: "IntelliJ IDEA 2025.3.1.1",
|
||
Source: "programs",
|
||
Vendor: "JetBrains s.r.o.",
|
||
Version: "253.31033.200",
|
||
}
|
||
MutateSoftwareOnIngestion(t.Context(), jetbrainsIntelliJ, slog.New(slog.DiscardHandler))
|
||
assert.Equal(t, "2025.3.1.1", jetbrainsIntelliJ.Version)
|
||
|
||
// Test JetBrains Toolbox is excluded (reports correct version)
|
||
jetbrainsToolbox := &fleet.Software{
|
||
Name: "JetBrains Toolbox",
|
||
Source: "programs",
|
||
Vendor: "JetBrains s.r.o.",
|
||
Version: "2.6.2.38498",
|
||
}
|
||
MutateSoftwareOnIngestion(t.Context(), jetbrainsToolbox, slog.New(slog.DiscardHandler))
|
||
assert.Equal(t, "2.6.2.38498", jetbrainsToolbox.Version)
|
||
|
||
// Test JetBrains software without version in name is not transformed
|
||
jetbrainsNoVersionInName := &fleet.Software{
|
||
Name: "IntelliJ IDEA",
|
||
Source: "programs",
|
||
Vendor: "JetBrains s.r.o.",
|
||
Version: "253.31033.139",
|
||
}
|
||
MutateSoftwareOnIngestion(t.Context(), jetbrainsNoVersionInName, slog.New(slog.DiscardHandler))
|
||
assert.Equal(t, "253.31033.139", jetbrainsNoVersionInName.Version)
|
||
|
||
// Test non-JetBrains software is not transformed
|
||
nonJetbrains := &fleet.Software{
|
||
Name: "Some Software 2025.1.1",
|
||
Source: "programs",
|
||
Vendor: "Some Vendor",
|
||
Version: "253.31033.139",
|
||
}
|
||
MutateSoftwareOnIngestion(t.Context(), nonJetbrains, slog.New(slog.DiscardHandler))
|
||
assert.Equal(t, "253.31033.139", nonJetbrains.Version)
|
||
|
||
// Test RHEL kernel version and release are joined
|
||
rhelKernel := &fleet.Software{
|
||
Name: "kernel",
|
||
Source: "rpm_packages",
|
||
Version: "5.14.0",
|
||
Release: "362.24.1.el9_3",
|
||
}
|
||
MutateSoftwareOnIngestion(t.Context(), rhelKernel, slog.New(slog.DiscardHandler))
|
||
assert.Equal(t, "5.14.0-362.24.1.el9_3", rhelKernel.Version)
|
||
assert.Equal(t, "", rhelKernel.Release)
|
||
|
||
// Test RHEL kernel without release is not modified
|
||
rhelKernelNoRelease := &fleet.Software{
|
||
Name: "kernel",
|
||
Source: "rpm_packages",
|
||
Version: "5.14.0",
|
||
Release: "",
|
||
}
|
||
MutateSoftwareOnIngestion(t.Context(), rhelKernelNoRelease, slog.New(slog.DiscardHandler))
|
||
assert.Equal(t, "5.14.0", rhelKernelNoRelease.Version)
|
||
|
||
// Test non-kernel rpm package is not modified
|
||
rpmPackage := &fleet.Software{
|
||
Name: "openssl",
|
||
Source: "rpm_packages",
|
||
Version: "3.0.7",
|
||
Release: "24.el9",
|
||
}
|
||
MutateSoftwareOnIngestion(t.Context(), rpmPackage, slog.New(slog.DiscardHandler))
|
||
assert.Equal(t, "3.0.7", rpmPackage.Version)
|
||
assert.Equal(t, "24.el9", rpmPackage.Release)
|
||
}
|
||
|
||
func TestDetailQueryNetworkInterfaces(t *testing.T) {
|
||
var initialHost fleet.Host
|
||
host := initialHost
|
||
|
||
ingest := GetDetailQueries(t.Context(), config.FleetConfig{}, nil, nil, Integrations{}, nil)["network_interface_unix"].IngestFunc
|
||
|
||
assert.NoError(t, ingest(t.Context(), slog.New(slog.DiscardHandler), &host, nil))
|
||
assert.Equal(t, initialHost, host)
|
||
|
||
var rows []map[string]string
|
||
require.NoError(t, json.Unmarshal([]byte(`
|
||
[
|
||
{"address":"10.0.1.2","mac":"bc:d0:74:4b:10:6d"}
|
||
]`),
|
||
&rows,
|
||
))
|
||
|
||
assert.NoError(t, ingest(t.Context(), slog.New(slog.DiscardHandler), &host, rows))
|
||
assert.Equal(t, "10.0.1.2", host.PrimaryIP)
|
||
assert.Equal(t, "bc:d0:74:4b:10:6d", host.PrimaryMac)
|
||
|
||
rows = make([]map[string]string, 1)
|
||
require.NoError(
|
||
t, json.Unmarshal(
|
||
[]byte(`
|
||
[
|
||
{"address":"fd7a:115c:a1e0::d401:6637","mac":"b2:a2:e4:62:0f:1e"}
|
||
]`),
|
||
&rows,
|
||
),
|
||
)
|
||
assert.NoError(t, ingest(t.Context(), slog.New(slog.DiscardHandler), &host, rows))
|
||
assert.Equal(t, "fd7a:115c:a1e0::d401:6637", host.PrimaryIP)
|
||
assert.Equal(t, "b2:a2:e4:62:0f:1e", host.PrimaryMac)
|
||
}
|
||
|
||
func TestDetailQueryScheduledQueryStats(t *testing.T) {
|
||
host := fleet.Host{ID: 1}
|
||
ds := new(mock.Store)
|
||
task := async.NewTask(ds, nil, clock.C, &config.FleetConfig{
|
||
Osquery: config.OsqueryConfig{EnableAsyncHostProcessing: "false"},
|
||
})
|
||
|
||
var gotPackStats []fleet.PackStats
|
||
ds.SaveHostPackStatsFunc = func(ctx context.Context, teamID *uint, hostID uint, stats []fleet.PackStats) error {
|
||
if hostID != host.ID {
|
||
return errors.New("not found")
|
||
}
|
||
gotPackStats = stats
|
||
return nil
|
||
}
|
||
|
||
ingest := GetDetailQueries(t.Context(), config.FleetConfig{App: config.AppConfig{EnableScheduledQueryStats: true}}, nil, nil, Integrations{}, nil)["scheduled_query_stats"].DirectTaskIngestFunc
|
||
|
||
ctx := t.Context()
|
||
assert.NoError(t, ingest(ctx, slog.New(slog.DiscardHandler), &host, task, nil))
|
||
assert.Len(t, host.PackStats, 0)
|
||
|
||
resJSON := `
|
||
[
|
||
{
|
||
"average_memory":"33",
|
||
"delimiter":"/",
|
||
"denylisted":"0",
|
||
"executions":"1",
|
||
"interval":"33",
|
||
"last_executed":"1620325191",
|
||
"name":"pack/pack-2/time",
|
||
"output_size":"",
|
||
"query":"SELECT * FROM time",
|
||
"system_time":"100",
|
||
"user_time":"60",
|
||
"wall_time":"180"
|
||
},
|
||
{
|
||
"average_memory":"8000",
|
||
"delimiter":"/",
|
||
"denylisted":"0",
|
||
"executions":"164",
|
||
"interval":"30",
|
||
"last_executed":"1620325191",
|
||
"name":"pack/test/osquery info",
|
||
"output_size":"1337",
|
||
"query":"SELECT * FROM osquery_info",
|
||
"system_time":"150",
|
||
"user_time":"180",
|
||
"wall_time_ms":"0"
|
||
},
|
||
{
|
||
"average_memory":"50400",
|
||
"delimiter":"/",
|
||
"denylisted":"1",
|
||
"executions":"188",
|
||
"interval":"30",
|
||
"last_executed":"1620325203",
|
||
"name":"pack/test/processes?",
|
||
"output_size":"",
|
||
"query":"SELECT * FROM processes",
|
||
"system_time":"140",
|
||
"user_time":"190",
|
||
"wall_time":"1111",
|
||
"wall_time_ms":"1"
|
||
},
|
||
{
|
||
"average_memory":"0",
|
||
"delimiter":"/",
|
||
"denylisted":"0",
|
||
"executions":"1",
|
||
"interval":"3600",
|
||
"last_executed":"1620323381",
|
||
"name":"pack/test/processes?-1",
|
||
"output_size":"",
|
||
"query":"SELECT * FROM processes",
|
||
"system_time":"0",
|
||
"user_time":"0",
|
||
"wall_time_ms":"0"
|
||
},
|
||
{
|
||
"average_memory":"0",
|
||
"delimiter":"/",
|
||
"denylisted":"0",
|
||
"executions":"105",
|
||
"interval":"47",
|
||
"last_executed":"1620325190",
|
||
"name":"pack/test/time",
|
||
"output_size":"",
|
||
"query":"SELECT * FROM time",
|
||
"system_time":"70",
|
||
"user_time":"50",
|
||
"wall_time_ms":"1"
|
||
}
|
||
]
|
||
`
|
||
|
||
var rows []map[string]string
|
||
require.NoError(t, json.Unmarshal([]byte(resJSON), &rows))
|
||
|
||
assert.NoError(t, ingest(ctx, slog.New(slog.DiscardHandler), &host, task, rows))
|
||
assert.Len(t, gotPackStats, 2)
|
||
sort.Slice(gotPackStats, func(i, j int) bool {
|
||
return gotPackStats[i].PackName < gotPackStats[j].PackName
|
||
})
|
||
assert.Equal(t, gotPackStats[0].PackName, "pack-2")
|
||
assert.ElementsMatch(t, gotPackStats[0].QueryStats,
|
||
[]fleet.ScheduledQueryStats{
|
||
{
|
||
ScheduledQueryName: "time",
|
||
PackName: "pack-2",
|
||
AverageMemory: 33,
|
||
Denylisted: false,
|
||
Executions: 1,
|
||
Interval: 33,
|
||
LastExecuted: time.Unix(1620325191, 0).UTC(),
|
||
OutputSize: 0,
|
||
SystemTime: 100,
|
||
UserTime: 60,
|
||
WallTimeMs: 180 * 1000,
|
||
},
|
||
},
|
||
)
|
||
assert.Equal(t, gotPackStats[1].PackName, "test")
|
||
assert.ElementsMatch(t, gotPackStats[1].QueryStats,
|
||
[]fleet.ScheduledQueryStats{
|
||
{
|
||
ScheduledQueryName: "osquery info",
|
||
PackName: "test",
|
||
AverageMemory: 8000,
|
||
Denylisted: false,
|
||
Executions: 164,
|
||
Interval: 30,
|
||
LastExecuted: time.Unix(1620325191, 0).UTC(),
|
||
OutputSize: 1337,
|
||
SystemTime: 150,
|
||
UserTime: 180,
|
||
WallTimeMs: 0,
|
||
},
|
||
{
|
||
ScheduledQueryName: "processes?",
|
||
PackName: "test",
|
||
AverageMemory: 50400,
|
||
Denylisted: true,
|
||
Executions: 188,
|
||
Interval: 30,
|
||
LastExecuted: time.Unix(1620325203, 0).UTC(),
|
||
OutputSize: 0,
|
||
SystemTime: 140,
|
||
UserTime: 190,
|
||
WallTimeMs: 1,
|
||
},
|
||
{
|
||
ScheduledQueryName: "processes?-1",
|
||
PackName: "test",
|
||
AverageMemory: 0,
|
||
Denylisted: false,
|
||
Executions: 1,
|
||
Interval: 3600,
|
||
LastExecuted: time.Unix(1620323381, 0).UTC(),
|
||
OutputSize: 0,
|
||
SystemTime: 0,
|
||
UserTime: 0,
|
||
WallTimeMs: 0,
|
||
},
|
||
{
|
||
ScheduledQueryName: "time",
|
||
PackName: "test",
|
||
AverageMemory: 0,
|
||
Denylisted: false,
|
||
Executions: 105,
|
||
Interval: 47,
|
||
LastExecuted: time.Unix(1620325190, 0).UTC(),
|
||
OutputSize: 0,
|
||
SystemTime: 70,
|
||
UserTime: 50,
|
||
WallTimeMs: 1,
|
||
},
|
||
},
|
||
)
|
||
|
||
assert.NoError(t, ingest(ctx, slog.New(slog.DiscardHandler), &host, task, nil))
|
||
assert.Len(t, gotPackStats, 0)
|
||
}
|
||
|
||
func sortedKeysCompare(t *testing.T, m map[string]DetailQuery, expectedKeys []string) {
|
||
var keys []string
|
||
for key := range m {
|
||
keys = append(keys, key)
|
||
}
|
||
assert.ElementsMatch(t, keys, expectedKeys)
|
||
}
|
||
|
||
func TestGetDetailQueries(t *testing.T) {
|
||
queriesNoConfig := GetDetailQueries(t.Context(), config.FleetConfig{}, nil, nil, Integrations{}, nil)
|
||
|
||
baseQueries := []string{
|
||
"network_interface_unix",
|
||
"network_interface_windows",
|
||
"network_interface_chrome",
|
||
"os_version",
|
||
"os_version_windows",
|
||
"osquery_flags",
|
||
"osquery_info",
|
||
"system_info",
|
||
"uptime",
|
||
"disk_space_unix",
|
||
"disk_space_darwin",
|
||
"disk_space_darwin_legacy",
|
||
"disk_space_windows",
|
||
"mdm",
|
||
"mdm_windows",
|
||
"munki_info",
|
||
"google_chrome_profiles",
|
||
"battery",
|
||
"os_windows",
|
||
"os_unix_like",
|
||
"os_chrome",
|
||
"windows_update_history",
|
||
"kubequery_info",
|
||
"orbit_info",
|
||
"disk_encryption_darwin",
|
||
"disk_encryption_linux",
|
||
"disk_encryption_windows",
|
||
"chromeos_profile_user_info",
|
||
"certificates_darwin",
|
||
"certificates_windows",
|
||
}
|
||
|
||
require.Len(t, queriesNoConfig, len(baseQueries))
|
||
sortedKeysCompare(t, queriesNoConfig, baseQueries)
|
||
|
||
queriesWithoutWinOSVuln := GetDetailQueries(t.Context(), config.FleetConfig{Vulnerabilities: config.VulnerabilitiesConfig{DisableWinOSVulnerabilities: true}}, nil, nil, Integrations{}, nil)
|
||
require.Len(t, queriesWithoutWinOSVuln, 29)
|
||
|
||
queriesWithUsers := GetDetailQueries(t.Context(), config.FleetConfig{App: config.AppConfig{EnableScheduledQueryStats: true}}, nil, &fleet.Features{EnableHostUsers: true}, Integrations{}, nil)
|
||
qs := baseQueries
|
||
qs = append(qs, "users", "users_chrome", "scheduled_query_stats")
|
||
require.Len(t, queriesWithUsers, len(qs))
|
||
sortedKeysCompare(t, queriesWithUsers, qs)
|
||
|
||
queriesWithUsersAndSoftware := GetDetailQueries(t.Context(), config.FleetConfig{App: config.AppConfig{EnableScheduledQueryStats: true}}, nil, &fleet.Features{EnableHostUsers: true, EnableSoftwareInventory: true}, Integrations{}, nil)
|
||
qs = baseQueries
|
||
qs = append(qs, "users", "users_chrome", "software_macos", "software_linux", "software_windows", "software_vscode_extensions", "software_jetbrains_plugins", "software_linux_fleetd_pacman",
|
||
"software_chrome", "software_python_packages", "software_python_packages_with_users_dir", "scheduled_query_stats", "software_macos_firefox", "software_macos_codesign", "software_macos_executable_sha256", "software_windows_last_opened_at", "software_deb_last_opened_at", "software_rpm_last_opened_at", "software_windows_acrobat_dc", "software_go_binaries")
|
||
require.Len(t, queriesWithUsersAndSoftware, len(qs))
|
||
sortedKeysCompare(t, queriesWithUsersAndSoftware, qs)
|
||
|
||
// test that appropriate mdm queries are added based on app config
|
||
var mdmQueriesBase, mdmQueriesWindows []string
|
||
for k, q := range mdmQueries {
|
||
switch {
|
||
case slices.Equal(q.Platforms, []string{"windows"}):
|
||
mdmQueriesWindows = append(mdmQueriesWindows, k)
|
||
default:
|
||
mdmQueriesBase = append(mdmQueriesBase, k)
|
||
}
|
||
}
|
||
ac := fleet.AppConfig{}
|
||
ac.MDM.EnabledAndConfigured = true
|
||
// windows mdm is disabled by default, windows mdm queries should not be present
|
||
gotQueries := GetDetailQueries(t.Context(), config.FleetConfig{}, &ac, nil, Integrations{}, nil)
|
||
wantQueries := baseQueries
|
||
wantQueries = append(wantQueries, mdmQueriesBase...)
|
||
require.Len(t, gotQueries, len(wantQueries))
|
||
sortedKeysCompare(t, gotQueries, wantQueries)
|
||
// enable windows mdm, windows mdm queries should be present
|
||
ac.MDM.WindowsEnabledAndConfigured = true
|
||
gotQueries = GetDetailQueries(t.Context(), config.FleetConfig{}, &ac, nil, Integrations{}, nil)
|
||
wantQueries = append(wantQueries, mdmQueriesWindows...)
|
||
require.Len(t, gotQueries, len(wantQueries))
|
||
sortedKeysCompare(t, gotQueries, wantQueries)
|
||
|
||
// Check that TPM PIN verify queries are only added iff RequireBitLockerPIN is set
|
||
|
||
testCases := []struct {
|
||
name string
|
||
ac fleet.AppConfig
|
||
want []string
|
||
}{
|
||
{
|
||
name: "windows MDM not enabled",
|
||
ac: fleet.AppConfig{},
|
||
},
|
||
{
|
||
name: "windows MDM is enabled but disk encryption is not enabled",
|
||
ac: fleet.AppConfig{
|
||
MDM: fleet.MDM{
|
||
WindowsEnabledAndConfigured: true,
|
||
},
|
||
},
|
||
},
|
||
{
|
||
name: "windows MDM is enabled with disk encryption but TPM PIN is not enforced",
|
||
ac: fleet.AppConfig{
|
||
MDM: fleet.MDM{
|
||
WindowsEnabledAndConfigured: true,
|
||
EnableDiskEncryption: optjson.SetBool(true),
|
||
},
|
||
},
|
||
},
|
||
{
|
||
name: "windows MDM is enabled with disk encryption and TPM PIN is enforced",
|
||
ac: fleet.AppConfig{
|
||
MDM: fleet.MDM{
|
||
WindowsEnabledAndConfigured: true,
|
||
EnableDiskEncryption: optjson.SetBool(true),
|
||
RequireBitLockerPIN: optjson.SetBool(true),
|
||
},
|
||
},
|
||
want: maps.Keys(tpmPINQueries),
|
||
},
|
||
}
|
||
|
||
for _, tt := range testCases {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
got := GetDetailQueries(t.Context(), config.FleetConfig{}, &tt.ac, nil, Integrations{}, nil)
|
||
for _, name := range tt.want {
|
||
_, ok := got[name]
|
||
require.True(t, ok)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestDiskSpaceDarwinLegacyQueryExcludesGigsAll(t *testing.T) {
|
||
queries := GetDetailQueries(t.Context(), config.FleetConfig{}, nil, nil, Integrations{}, nil)
|
||
|
||
// disk_space_darwin_legacy targets darwin where the `disk_space`` table is
|
||
// unavailable. gigs_all_disk_space is only ingested for Linux hosts, so the
|
||
// legacy darwin query should not include it.
|
||
legacy, ok := queries["disk_space_darwin_legacy"]
|
||
require.True(t, ok)
|
||
assert.NotContains(t, legacy.Query, "gigs_all_disk_space")
|
||
|
||
// disk_space_unix targets Linux and should include gigs_all_disk_space.
|
||
unix, ok := queries["disk_space_unix"]
|
||
require.True(t, ok)
|
||
assert.Contains(t, unix.Query, "gigs_all_disk_space")
|
||
}
|
||
|
||
func TestDetailQueriesOSVersionUnixLike(t *testing.T) {
|
||
var initialHost fleet.Host
|
||
host := initialHost
|
||
|
||
ingest := GetDetailQueries(t.Context(), config.FleetConfig{}, nil, nil, Integrations{}, nil)["os_version"].IngestFunc
|
||
|
||
assert.NoError(t, ingest(t.Context(), slog.New(slog.DiscardHandler), &host, nil))
|
||
assert.Equal(t, initialHost, host)
|
||
|
||
// Rolling release for archlinux
|
||
var rows []map[string]string
|
||
require.NoError(t, json.Unmarshal([]byte(`
|
||
[{
|
||
"hostname": "kube2",
|
||
"arch": "x86_64",
|
||
"build": "rolling",
|
||
"codename": "",
|
||
"major": "0",
|
||
"minor": "0",
|
||
"name": "Arch Linux",
|
||
"patch": "0",
|
||
"platform": "arch",
|
||
"platform_like": "",
|
||
"version": ""
|
||
}]`),
|
||
&rows,
|
||
))
|
||
|
||
assert.NoError(t, ingest(t.Context(), slog.New(slog.DiscardHandler), &host, rows))
|
||
assert.Equal(t, "Arch Linux rolling", host.OSVersion)
|
||
|
||
// Simulate a linux with a proper version
|
||
require.NoError(t, json.Unmarshal([]byte(`
|
||
[{
|
||
"hostname": "kube2",
|
||
"arch": "x86_64",
|
||
"build": "rolling",
|
||
"codename": "",
|
||
"major": "1",
|
||
"minor": "2",
|
||
"name": "Arch Linux",
|
||
"patch": "3",
|
||
"platform": "arch",
|
||
"platform_like": "",
|
||
"version": ""
|
||
}]`),
|
||
&rows,
|
||
))
|
||
|
||
assert.NoError(t, ingest(t.Context(), slog.New(slog.DiscardHandler), &host, rows))
|
||
assert.Equal(t, "Arch Linux 1.2.3", host.OSVersion)
|
||
|
||
// Simulate Ubuntu host with incorrect `patch` number
|
||
require.NoError(t, json.Unmarshal([]byte(`
|
||
[{
|
||
"hostname": "kube2",
|
||
"arch": "x86_64",
|
||
"build": "",
|
||
"codename": "bionic",
|
||
"major": "18",
|
||
"minor": "4",
|
||
"name": "Ubuntu",
|
||
"patch": "0",
|
||
"platform": "ubuntu",
|
||
"platform_like": "debian",
|
||
"version": "18.04.5 LTS (Bionic Beaver)"
|
||
}]`),
|
||
&rows,
|
||
))
|
||
|
||
assert.NoError(t, ingest(t.Context(), slog.New(slog.DiscardHandler), &host, rows))
|
||
assert.Equal(t, "Ubuntu 18.04.5 LTS", host.OSVersion)
|
||
}
|
||
|
||
func TestDetailQueriesOSVersionWindows(t *testing.T) {
|
||
var initialHost fleet.Host
|
||
host := initialHost
|
||
|
||
ingest := GetDetailQueries(t.Context(), config.FleetConfig{}, nil, nil, Integrations{}, nil)["os_version_windows"].IngestFunc
|
||
|
||
assert.NoError(t, ingest(t.Context(), slog.New(slog.DiscardHandler), &host, nil))
|
||
assert.Equal(t, initialHost, host)
|
||
|
||
var rows []map[string]string
|
||
require.NoError(t, json.Unmarshal([]byte(`
|
||
[{
|
||
"hostname": "WinBox",
|
||
"arch": "64-bit",
|
||
"build": "22000",
|
||
"codename": "Microsoft Windows 11 Enterprise",
|
||
"major": "10",
|
||
"minor": "0",
|
||
"name": "Microsoft Windows 11 Enterprise",
|
||
"patch": "",
|
||
"platform": "windows",
|
||
"platform_like": "windows",
|
||
"version": "10.0.22000",
|
||
"display_version": "21H2",
|
||
"release_id": ""
|
||
}]`),
|
||
&rows,
|
||
))
|
||
|
||
assert.NoError(t, ingest(t.Context(), slog.New(slog.DiscardHandler), &host, rows))
|
||
assert.Equal(t, "Windows 11 Enterprise 21H2 10.0.22000", host.OSVersion)
|
||
|
||
require.NoError(t, json.Unmarshal([]byte(`
|
||
[{
|
||
"hostname": "WinBox",
|
||
"arch": "64-bit",
|
||
"build": "17763",
|
||
"codename": "Microsoft Windows 10 Enterprise LTSC",
|
||
"major": "10",
|
||
"minor": "0",
|
||
"name": "Microsoft Windows 10 Enterprise LTSC",
|
||
"patch": "",
|
||
"platform": "windows",
|
||
"platform_like": "windows",
|
||
"version": "10.0.17763",
|
||
"display_version": "",
|
||
"release_id": "1809"
|
||
}]`),
|
||
&rows,
|
||
))
|
||
|
||
assert.NoError(t, ingest(t.Context(), slog.New(slog.DiscardHandler), &host, rows))
|
||
assert.Equal(t, "Windows 10 Enterprise LTSC 10.0.17763", host.OSVersion)
|
||
}
|
||
|
||
func TestDetailQueriesOSVersionChrome(t *testing.T) {
|
||
var initialHost fleet.Host
|
||
host := initialHost
|
||
|
||
ingest := GetDetailQueries(t.Context(), config.FleetConfig{}, nil, nil, Integrations{}, nil)["os_version"].IngestFunc
|
||
|
||
assert.NoError(t, ingest(t.Context(), slog.New(slog.DiscardHandler), &host, nil))
|
||
assert.Equal(t, initialHost, host)
|
||
|
||
var rows []map[string]string
|
||
require.NoError(t, json.Unmarshal([]byte(`
|
||
[{
|
||
"hostname": "chromeo",
|
||
"arch": "x86_64",
|
||
"build": "chrome-build",
|
||
"codename": "",
|
||
"major": "1",
|
||
"minor": "3",
|
||
"name": "chromeos",
|
||
"patch": "7",
|
||
"platform": "chrome",
|
||
"platform_like": "chrome",
|
||
"version": "1.3.3.7"
|
||
}]`),
|
||
&rows,
|
||
))
|
||
|
||
assert.NoError(t, ingest(t.Context(), slog.New(slog.DiscardHandler), &host, rows))
|
||
assert.Equal(t, "chromeos 1.3.3.7", host.OSVersion)
|
||
}
|
||
|
||
func TestDirectIngestMDMMac(t *testing.T) {
|
||
ds := new(mock.Store)
|
||
var host fleet.Host
|
||
|
||
cases := []struct {
|
||
name string
|
||
got map[string]string
|
||
wantParams []any
|
||
wantErr string
|
||
enrollRef string
|
||
}{
|
||
{
|
||
"empty server URL",
|
||
map[string]string{
|
||
"enrolled": "false",
|
||
"installed_from_dep": "",
|
||
"server_url": "",
|
||
},
|
||
[]any{false, false, "", false, fleet.UnknownMDMName},
|
||
"",
|
||
"",
|
||
},
|
||
{
|
||
"with Fleet payload identifier",
|
||
map[string]string{
|
||
"enrolled": "true",
|
||
"installed_from_dep": "true",
|
||
"server_url": "https://test.example.com",
|
||
"payload_identifier": apple_mdm.FleetPayloadIdentifier,
|
||
},
|
||
[]any{false, true, "https://test.example.com", true, fleet.WellKnownMDMFleet},
|
||
"",
|
||
"",
|
||
},
|
||
{
|
||
"with a query string on the server URL",
|
||
map[string]string{
|
||
"enrolled": "true",
|
||
"installed_from_dep": "true",
|
||
"server_url": "https://jamf.com/1/some/path?one=1&two=2",
|
||
},
|
||
[]any{false, true, "https://jamf.com/1/some/path", true, fleet.WellKnownMDMJamf},
|
||
"",
|
||
"",
|
||
},
|
||
{
|
||
"with invalid installed_from_dep",
|
||
map[string]string{
|
||
"enrolled": "true",
|
||
"installed_from_dep": "invalid",
|
||
"server_url": "https://jamf.com/1/some/path?one=1&two=2",
|
||
},
|
||
[]any{},
|
||
"parsing installed_from_dep",
|
||
"",
|
||
},
|
||
{
|
||
"with invalid enrolled",
|
||
map[string]string{
|
||
"enrolled": "invalid",
|
||
"installed_from_dep": "false",
|
||
"server_url": "https://jamf.com/1/some/path?one=1&two=2",
|
||
},
|
||
[]any{},
|
||
"parsing enrolled",
|
||
"",
|
||
},
|
||
{
|
||
"with invalid server_url",
|
||
map[string]string{
|
||
"enrolled": "false",
|
||
"installed_from_dep": "false",
|
||
"server_url": "ht tp://foo.com",
|
||
},
|
||
[]any{},
|
||
"parsing server_url",
|
||
"",
|
||
},
|
||
{
|
||
"with invalid enrollment reference",
|
||
map[string]string{
|
||
"enrolled": "true",
|
||
"installed_from_dep": "true",
|
||
"server_url": "https://test.example.com?enroll_reference=foobar",
|
||
"payload_identifier": apple_mdm.FleetPayloadIdentifier,
|
||
},
|
||
[]any{false, true, "https://test.example.com", true, fleet.WellKnownMDMFleet},
|
||
"",
|
||
"foobar",
|
||
},
|
||
}
|
||
|
||
for _, c := range cases {
|
||
t.Run(c.name, func(t *testing.T) {
|
||
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
||
return &fleet.AppConfig{
|
||
MDM: fleet.MDM{
|
||
MacOSSetup: fleet.MacOSSetup{
|
||
EnableEndUserAuthentication: true,
|
||
},
|
||
},
|
||
}, nil
|
||
}
|
||
ds.SetOrUpdateMDMDataFunc = func(ctx context.Context, hostID uint, isServer, enrolled bool, serverURL string, installedFromDep bool, name string, fleetEnrollmentRef string, isPersonalEnrollment bool) error {
|
||
require.Equal(t, isServer, c.wantParams[0])
|
||
require.Equal(t, enrolled, c.wantParams[1])
|
||
require.Equal(t, serverURL, c.wantParams[2])
|
||
require.Equal(t, installedFromDep, c.wantParams[3])
|
||
require.Equal(t, name, c.wantParams[4])
|
||
require.Equal(t, fleetEnrollmentRef, c.enrollRef)
|
||
require.False(t, isPersonalEnrollment)
|
||
return nil
|
||
}
|
||
|
||
err := directIngestMDMMac(t.Context(), slog.New(slog.DiscardHandler), &host, ds, []map[string]string{c.got})
|
||
if c.wantErr != "" {
|
||
require.ErrorContains(t, err, c.wantErr)
|
||
require.False(t, ds.SetOrUpdateMDMDataFuncInvoked)
|
||
} else {
|
||
require.True(t, ds.SetOrUpdateMDMDataFuncInvoked)
|
||
require.NoError(t, err)
|
||
}
|
||
ds.SetOrUpdateMDMDataFuncInvoked = false
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestDirectIngestMDMFleetEnrollRef(t *testing.T) {
|
||
ds := new(mock.Store)
|
||
var host fleet.Host
|
||
|
||
generateRows := func(serverURL, payloadIdentifier string) []map[string]string {
|
||
return []map[string]string{
|
||
{
|
||
"enrolled": "true",
|
||
"installed_from_dep": "true",
|
||
"server_url": serverURL,
|
||
"payload_identifier": payloadIdentifier,
|
||
},
|
||
}
|
||
}
|
||
|
||
type testCase struct {
|
||
name string
|
||
mdmData []map[string]string
|
||
wantServerURL string
|
||
wantEnrollRef string
|
||
}
|
||
|
||
for _, tc := range []testCase{
|
||
{
|
||
name: "Fleet enroll_reference",
|
||
mdmData: generateRows("https://test.example.com?enroll_reference=test-reference", apple_mdm.FleetPayloadIdentifier),
|
||
wantServerURL: "https://test.example.com",
|
||
wantEnrollRef: "test-reference",
|
||
},
|
||
{
|
||
name: "Fleet no enroll_reference",
|
||
mdmData: generateRows("https://test.example.com", apple_mdm.FleetPayloadIdentifier),
|
||
wantServerURL: "https://test.example.com",
|
||
wantEnrollRef: "",
|
||
},
|
||
{
|
||
name: "Fleet enrollment_reference",
|
||
mdmData: generateRows("https://test.example.com?enrollment_reference=test-reference", apple_mdm.FleetPayloadIdentifier),
|
||
wantServerURL: "https://test.example.com",
|
||
wantEnrollRef: "test-reference",
|
||
},
|
||
{
|
||
name: "Fleet enroll_reference with other query params",
|
||
mdmData: generateRows("https://test.example.com?token=abcdefg&enroll_reference=test-reference", apple_mdm.FleetPayloadIdentifier),
|
||
wantServerURL: "https://test.example.com",
|
||
wantEnrollRef: "test-reference",
|
||
},
|
||
{
|
||
name: "non-Fleet enroll_reference",
|
||
mdmData: generateRows("https://test.example.com?enroll_reference=test-reference", "com.unknown.mdm"),
|
||
wantServerURL: "https://test.example.com",
|
||
wantEnrollRef: "",
|
||
},
|
||
} {
|
||
t.Run(tc.name, func(t *testing.T) {
|
||
ds.SetOrUpdateMDMDataFunc = func(ctx context.Context, hostID uint, isServer, enrolled bool, serverURL string, installedFromDep bool, name string, fleetEnrollmentRef string, isPersonalEnrollment bool) error {
|
||
require.False(t, isServer)
|
||
require.True(t, enrolled)
|
||
require.True(t, installedFromDep)
|
||
require.False(t, isPersonalEnrollment)
|
||
|
||
require.Equal(t, tc.wantServerURL, serverURL)
|
||
require.Equal(t, tc.wantEnrollRef, fleetEnrollmentRef)
|
||
if tc.wantEnrollRef != "" {
|
||
require.NotContains(t, serverURL, tc.wantEnrollRef) // query string is removed
|
||
}
|
||
if tc.mdmData[0]["payload_identifier"] == apple_mdm.FleetPayloadIdentifier {
|
||
require.Equal(t, name, fleet.WellKnownMDMFleet)
|
||
} else {
|
||
require.Equal(t, name, fleet.UnknownMDMName)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
||
return &fleet.AppConfig{
|
||
MDM: fleet.MDM{
|
||
MacOSSetup: fleet.MacOSSetup{
|
||
EnableEndUserAuthentication: true,
|
||
},
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
err := directIngestMDMMac(t.Context(), slog.New(slog.DiscardHandler), &host, ds, tc.mdmData)
|
||
require.NoError(t, err)
|
||
require.True(t, ds.SetOrUpdateMDMDataFuncInvoked)
|
||
ds.SetOrUpdateMDMDataFuncInvoked = false
|
||
})
|
||
}
|
||
|
||
t.Run("end user authentication disabled", func(t *testing.T) {
|
||
// Test that email isn't set when end user authentication is disabled
|
||
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
||
return &fleet.AppConfig{
|
||
MDM: fleet.MDM{
|
||
MacOSSetup: fleet.MacOSSetup{
|
||
EnableEndUserAuthentication: false,
|
||
},
|
||
},
|
||
}, nil
|
||
}
|
||
ds.SetOrUpdateMDMDataFunc = func(ctx context.Context, hostID uint, isServer, enrolled bool, serverURL string, installedFromDep bool, name string, fleetEnrollmentRef string, isPersonalEnrollment bool) error {
|
||
require.False(t, isServer)
|
||
require.True(t, enrolled)
|
||
require.True(t, installedFromDep)
|
||
require.Equal(t, "https://test.example.com", serverURL)
|
||
require.Equal(t, "test-reference", fleetEnrollmentRef)
|
||
require.Equal(t, fleet.WellKnownMDMFleet, name)
|
||
require.False(t, isPersonalEnrollment)
|
||
|
||
return nil
|
||
}
|
||
|
||
t.Run("no team", func(t *testing.T) {
|
||
err := directIngestMDMMac(t.Context(), slog.New(slog.DiscardHandler), &host, ds, generateRows("https://test.example.com?enroll_reference=test-reference", apple_mdm.FleetPayloadIdentifier))
|
||
require.NoError(t, err)
|
||
require.True(t, ds.SetOrUpdateMDMDataFuncInvoked)
|
||
ds.SetOrUpdateMDMDataFuncInvoked = false
|
||
})
|
||
|
||
t.Run("team", func(t *testing.T) {
|
||
host.TeamID = ptr.Uint(1)
|
||
err := directIngestMDMMac(t.Context(), slog.New(slog.DiscardHandler), &host, ds, generateRows("https://test.example.com?enroll_reference=test-reference", apple_mdm.FleetPayloadIdentifier))
|
||
require.NoError(t, err)
|
||
require.True(t, ds.SetOrUpdateMDMDataFuncInvoked)
|
||
ds.SetOrUpdateMDMDataFuncInvoked = false
|
||
})
|
||
})
|
||
}
|
||
|
||
func TestDirectIngestMDMWindows(t *testing.T) {
|
||
ds := new(mock.Store)
|
||
cases := []struct {
|
||
name string
|
||
data []map[string]string
|
||
wantEnrolled bool
|
||
wantInstalledFromDep bool
|
||
wantIsServer bool
|
||
wantServerURL string
|
||
wantMDMSolName string
|
||
}{
|
||
{
|
||
name: "off empty server URL",
|
||
data: []map[string]string{
|
||
{
|
||
"discovery_service_url": "",
|
||
"aad_resource_id": "https://example.com",
|
||
"provider_id": "Some_ID",
|
||
"installation_type": "Client",
|
||
},
|
||
},
|
||
wantEnrolled: false,
|
||
wantInstalledFromDep: false,
|
||
wantIsServer: false,
|
||
wantServerURL: "",
|
||
},
|
||
{
|
||
name: "off missing aad_resource_id and server url",
|
||
data: []map[string]string{
|
||
{
|
||
"provider_id": "Some_ID",
|
||
"installation_type": "Client",
|
||
},
|
||
},
|
||
wantEnrolled: false,
|
||
wantInstalledFromDep: false,
|
||
wantIsServer: false,
|
||
wantServerURL: "",
|
||
},
|
||
{
|
||
name: "off no rows",
|
||
data: []map[string]string{},
|
||
wantEnrolled: false,
|
||
wantInstalledFromDep: false,
|
||
wantIsServer: false,
|
||
wantServerURL: "",
|
||
},
|
||
{
|
||
name: "on automatic",
|
||
data: []map[string]string{
|
||
{
|
||
"discovery_service_url": "https://example.com",
|
||
"aad_resource_id": "https://example.com",
|
||
"provider_id": "Some_ID",
|
||
"installation_type": "Client",
|
||
},
|
||
},
|
||
wantEnrolled: true,
|
||
wantInstalledFromDep: true,
|
||
wantIsServer: false,
|
||
wantServerURL: "https://example.com",
|
||
},
|
||
{
|
||
name: "on manual",
|
||
data: []map[string]string{
|
||
{
|
||
"discovery_service_url": "https://example.com",
|
||
"aad_resource_id": "",
|
||
"provider_id": "Local_Management",
|
||
"installation_type": "Client",
|
||
},
|
||
},
|
||
wantEnrolled: true,
|
||
wantInstalledFromDep: false,
|
||
wantIsServer: false,
|
||
wantServerURL: "https://example.com",
|
||
},
|
||
{
|
||
name: "on manual missing aad_resource_id",
|
||
data: []map[string]string{
|
||
{
|
||
"discovery_service_url": "https://example.com",
|
||
"provider_id": "Some_ID",
|
||
"installation_type": "Client",
|
||
},
|
||
},
|
||
wantEnrolled: true,
|
||
wantInstalledFromDep: false,
|
||
wantIsServer: false,
|
||
wantServerURL: "https://example.com",
|
||
},
|
||
{
|
||
name: "is_server",
|
||
data: []map[string]string{
|
||
{
|
||
"discovery_service_url": "https://example.com",
|
||
"aad_resource_id": "https://example.com",
|
||
"provider_id": "Some_ID",
|
||
"installation_type": "Windows SeRvEr 99.9",
|
||
},
|
||
},
|
||
wantEnrolled: true,
|
||
wantInstalledFromDep: true,
|
||
wantIsServer: true,
|
||
wantServerURL: "https://example.com",
|
||
},
|
||
|
||
// Test that names are being calculated correctly
|
||
|
||
{
|
||
name: "on manual jumpcloud",
|
||
data: []map[string]string{
|
||
{
|
||
"discovery_service_url": "https://jumpcloud.com",
|
||
"aad_resource_id": "",
|
||
"provider_id": "Local_Management",
|
||
"installation_type": "Client",
|
||
},
|
||
},
|
||
wantEnrolled: true,
|
||
wantInstalledFromDep: false,
|
||
wantIsServer: false,
|
||
wantServerURL: "https://jumpcloud.com",
|
||
wantMDMSolName: fleet.WellKnownMDMJumpCloud,
|
||
},
|
||
{
|
||
name: "on manual airwatch",
|
||
data: []map[string]string{
|
||
{
|
||
"discovery_service_url": "https://airwatch.com",
|
||
"aad_resource_id": "",
|
||
"provider_id": "Local_Management",
|
||
"installation_type": "Client",
|
||
},
|
||
},
|
||
wantEnrolled: true,
|
||
wantInstalledFromDep: false,
|
||
wantIsServer: false,
|
||
wantServerURL: "https://airwatch.com",
|
||
wantMDMSolName: fleet.WellKnownMDMVMWare,
|
||
},
|
||
{
|
||
name: "on manual awmdm",
|
||
data: []map[string]string{
|
||
{
|
||
"discovery_service_url": "https://awmdm.com",
|
||
"aad_resource_id": "",
|
||
"provider_id": "Local_Management",
|
||
"installation_type": "Client",
|
||
},
|
||
},
|
||
wantEnrolled: true,
|
||
wantInstalledFromDep: false,
|
||
wantIsServer: false,
|
||
wantServerURL: "https://awmdm.com",
|
||
wantMDMSolName: fleet.WellKnownMDMVMWare,
|
||
},
|
||
{
|
||
name: "on manual microsoft",
|
||
data: []map[string]string{
|
||
{
|
||
"discovery_service_url": "https://microsoft.com",
|
||
"aad_resource_id": "",
|
||
"provider_id": "Local_Management",
|
||
"installation_type": "Client",
|
||
},
|
||
},
|
||
wantEnrolled: true,
|
||
wantInstalledFromDep: false,
|
||
wantIsServer: false,
|
||
wantServerURL: "https://microsoft.com",
|
||
wantMDMSolName: fleet.WellKnownMDMIntune,
|
||
},
|
||
{
|
||
name: "on manual fleetdm cloud hosted",
|
||
data: []map[string]string{
|
||
{
|
||
"discovery_service_url": "https://fleetdm.com",
|
||
"aad_resource_id": "",
|
||
"provider_id": "Local_Management",
|
||
"installation_type": "Client",
|
||
},
|
||
},
|
||
wantEnrolled: true,
|
||
wantInstalledFromDep: false,
|
||
wantIsServer: false,
|
||
wantServerURL: "https://fleetdm.com",
|
||
wantMDMSolName: fleet.WellKnownMDMFleet,
|
||
},
|
||
|
||
{
|
||
name: "on manual fleetdm self hosted",
|
||
data: []map[string]string{
|
||
{
|
||
"discovery_service_url": "https://myinstall.local",
|
||
"aad_resource_id": "",
|
||
"provider_id": "Fleet",
|
||
"installation_type": "Client",
|
||
},
|
||
},
|
||
wantEnrolled: true,
|
||
wantInstalledFromDep: false,
|
||
wantIsServer: false,
|
||
wantServerURL: "https://myinstall.local",
|
||
wantMDMSolName: fleet.WellKnownMDMFleet,
|
||
},
|
||
}
|
||
|
||
for _, c := range cases {
|
||
t.Run(c.name, func(t *testing.T) {
|
||
ds.SetOrUpdateMDMDataFunc = func(ctx context.Context, hostID uint, isServer, enrolled bool, serverURL string, installedFromDep bool, name string, fleetEnrollmentRef string, isPersonalEnrollment bool) error {
|
||
require.Equal(t, c.wantEnrolled, enrolled)
|
||
require.Equal(t, c.wantInstalledFromDep, installedFromDep)
|
||
require.Equal(t, c.wantIsServer, isServer)
|
||
require.Equal(t, c.wantServerURL, serverURL)
|
||
require.Equal(t, c.wantMDMSolName, name)
|
||
require.Empty(t, fleetEnrollmentRef)
|
||
require.False(t, isPersonalEnrollment)
|
||
return nil
|
||
}
|
||
ds.MDMWindowsGetEnrolledDeviceWithHostUUIDFunc = func(ctx context.Context, hostUUID string) (*fleet.MDMWindowsEnrolledDevice, error) {
|
||
return nil, common_mysql.NotFound("MDMWindowsEnrolledDevice")
|
||
}
|
||
ds.SetOrUpdateMDMDataFuncInvoked = false
|
||
ds.MDMWindowsGetEnrolledDeviceWithHostUUIDFuncInvoked = false
|
||
|
||
err := directIngestMDMWindows(t.Context(), slog.New(slog.DiscardHandler), &fleet.Host{}, ds, c.data)
|
||
require.NoError(t, err)
|
||
require.True(t, ds.SetOrUpdateMDMDataFuncInvoked)
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestDirectIngestChromeProfiles(t *testing.T) {
|
||
ds := new(mock.Store)
|
||
ds.ReplaceHostDeviceMappingFunc = func(ctx context.Context, hostID uint, mapping []*fleet.HostDeviceMapping, source string) error {
|
||
require.Equal(t, hostID, uint(1))
|
||
require.Equal(t, mapping, []*fleet.HostDeviceMapping{
|
||
{HostID: hostID, Email: "test@example.com", Source: "google_chrome_profiles"},
|
||
{HostID: hostID, Email: "test+2@example.com", Source: "google_chrome_profiles"},
|
||
})
|
||
require.Equal(t, source, "google_chrome_profiles")
|
||
return nil
|
||
}
|
||
|
||
host := fleet.Host{
|
||
ID: 1,
|
||
}
|
||
|
||
err := directIngestChromeProfiles(t.Context(), slog.New(slog.DiscardHandler), &host, ds, []map[string]string{
|
||
{"email": "test@example.com"},
|
||
{"email": "test+2@example.com"},
|
||
})
|
||
|
||
require.NoError(t, err)
|
||
require.True(t, ds.ReplaceHostDeviceMappingFuncInvoked)
|
||
}
|
||
|
||
func TestDirectIngestBattery(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
input map[string]string
|
||
expectedBattery *fleet.HostBattery
|
||
}{
|
||
{
|
||
name: "max_capacity >= 80%, cycleCount < 1000",
|
||
input: map[string]string{"serial_number": "a", "cycle_count": "2", "designed_capacity": "3000", "max_capacity": "2400"},
|
||
expectedBattery: &fleet.HostBattery{HostID: uint(1), SerialNumber: "a", CycleCount: 2, Health: batteryStatusGood},
|
||
},
|
||
{
|
||
name: "max_capacity < 50%",
|
||
input: map[string]string{"serial_number": "b", "cycle_count": "3", "designed_capacity": "3000", "max_capacity": "2399"},
|
||
expectedBattery: &fleet.HostBattery{HostID: uint(1), SerialNumber: "b", CycleCount: 3, Health: batteryStatusDegraded},
|
||
},
|
||
{
|
||
name: "missing max_capacity",
|
||
input: map[string]string{"serial_number": "c", "cycle_count": "4", "designed_capacity": "3000", "max_capacity": ""},
|
||
expectedBattery: &fleet.HostBattery{HostID: uint(1), SerialNumber: "c", CycleCount: 4, Health: batteryStatusUnknown},
|
||
},
|
||
{
|
||
name: "missing designed_capacity and max_capacity",
|
||
input: map[string]string{"serial_number": "d", "cycle_count": "5", "designed_capacity": "", "max_capacity": ""},
|
||
expectedBattery: &fleet.HostBattery{HostID: uint(1), SerialNumber: "d", CycleCount: 5, Health: batteryStatusUnknown},
|
||
},
|
||
{
|
||
name: "missing designed_capacity",
|
||
input: map[string]string{"serial_number": "e", "cycle_count": "6", "designed_capacity": "", "max_capacity": "2000"},
|
||
expectedBattery: &fleet.HostBattery{HostID: uint(1), SerialNumber: "e", CycleCount: 6, Health: batteryStatusUnknown},
|
||
},
|
||
{
|
||
name: "invalid designed_capacity and max_capacity",
|
||
input: map[string]string{"serial_number": "f", "cycle_count": "7", "designed_capacity": "foo", "max_capacity": "bar"},
|
||
expectedBattery: &fleet.HostBattery{HostID: uint(1), SerialNumber: "f", CycleCount: 7, Health: batteryStatusUnknown},
|
||
},
|
||
{
|
||
name: "cycleCount >= 1000",
|
||
input: map[string]string{"serial_number": "g", "cycle_count": "1000", "designed_capacity": "3000", "max_capacity": "2400"},
|
||
expectedBattery: &fleet.HostBattery{HostID: uint(1), SerialNumber: "g", CycleCount: 1000, Health: batteryStatusDegraded},
|
||
},
|
||
{
|
||
name: "cycleCount >= 1000 with degraded health",
|
||
input: map[string]string{"serial_number": "h", "cycle_count": "1001", "designed_capacity": "3000", "max_capacity": "2399"},
|
||
expectedBattery: &fleet.HostBattery{HostID: uint(1), SerialNumber: "h", CycleCount: 1001, Health: batteryStatusDegraded},
|
||
},
|
||
{
|
||
name: "missing cycle_count",
|
||
input: map[string]string{"serial_number": "i", "cycle_count": "", "designed_capacity": "3000", "max_capacity": "2400"},
|
||
expectedBattery: &fleet.HostBattery{HostID: uint(1), SerialNumber: "i", CycleCount: 0, Health: batteryStatusGood},
|
||
},
|
||
{
|
||
name: "missing cycle_count with degraded health",
|
||
input: map[string]string{"serial_number": "j", "cycle_count": "", "designed_capacity": "3000", "max_capacity": "2399"},
|
||
expectedBattery: &fleet.HostBattery{HostID: uint(1), SerialNumber: "j", CycleCount: 0, Health: batteryStatusDegraded},
|
||
},
|
||
{
|
||
name: "invalid cycle_count",
|
||
input: map[string]string{"serial_number": "k", "cycle_count": "foo", "designed_capacity": "3000", "max_capacity": "2400"},
|
||
expectedBattery: &fleet.HostBattery{HostID: uint(1), SerialNumber: "k", CycleCount: 0, Health: batteryStatusGood},
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
ds := new(mock.Store)
|
||
|
||
ds.ReplaceHostBatteriesFunc = func(ctx context.Context, id uint, mappings []*fleet.HostBattery) error {
|
||
require.Len(t, mappings, 1)
|
||
require.Equal(t, tt.expectedBattery, mappings[0])
|
||
return nil
|
||
}
|
||
|
||
host := fleet.Host{
|
||
ID: 1,
|
||
}
|
||
|
||
err := directIngestBattery(t.Context(), slog.New(slog.DiscardHandler), &host, ds, []map[string]string{tt.input})
|
||
require.NoError(t, err)
|
||
require.True(t, ds.ReplaceHostBatteriesFuncInvoked)
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestDirectIngestOSWindows(t *testing.T) {
|
||
ds := new(mock.Store)
|
||
|
||
testCases := []struct {
|
||
expected fleet.OperatingSystem
|
||
data []map[string]string
|
||
}{
|
||
{
|
||
expected: fleet.OperatingSystem{
|
||
Name: "Microsoft Windows 11 Enterprise 21H2",
|
||
Version: "10.0.22000.795",
|
||
Arch: "64-bit",
|
||
KernelVersion: "10.0.22000.795",
|
||
DisplayVersion: "21H2",
|
||
},
|
||
data: []map[string]string{
|
||
{"name": "Microsoft Windows 11 Enterprise", "display_version": "21H2", "version": "10.0.22000.795", "arch": "64-bit"},
|
||
},
|
||
},
|
||
{
|
||
expected: fleet.OperatingSystem{
|
||
Name: "Microsoft Windows 10 Enterprise", // no display_version
|
||
Version: "10.0.17763.2183",
|
||
Arch: "64-bit",
|
||
KernelVersion: "10.0.17763.2183",
|
||
DisplayVersion: "",
|
||
},
|
||
data: []map[string]string{
|
||
{"name": "Microsoft Windows 10 Enterprise", "display_version": "", "version": "10.0.17763.2183", "arch": "64-bit"},
|
||
},
|
||
},
|
||
}
|
||
|
||
host := fleet.Host{ID: 1}
|
||
|
||
for _, tt := range testCases {
|
||
ds.UpdateHostOperatingSystemFunc = func(ctx context.Context, hostID uint, hostOS fleet.OperatingSystem) error {
|
||
require.Equal(t, host.ID, hostID)
|
||
require.Equal(t, tt.expected, hostOS)
|
||
return nil
|
||
}
|
||
|
||
err := directIngestOSWindows(t.Context(), slog.New(slog.DiscardHandler), &host, ds, tt.data)
|
||
require.NoError(t, err)
|
||
|
||
require.True(t, ds.UpdateHostOperatingSystemFuncInvoked)
|
||
ds.UpdateHostOperatingSystemFuncInvoked = false
|
||
}
|
||
}
|
||
|
||
func TestDirectIngestOSUnixLike(t *testing.T) {
|
||
ds := new(mock.Store)
|
||
|
||
for i, tc := range []struct {
|
||
data []map[string]string
|
||
expected fleet.OperatingSystem
|
||
}{
|
||
{
|
||
data: []map[string]string{
|
||
{
|
||
"name": "macOS",
|
||
"version": "12.5",
|
||
"major": "12",
|
||
"minor": "5",
|
||
"patch": "0",
|
||
"build": "21G72",
|
||
"arch": "x86_64",
|
||
"kernel_version": "21.6.0",
|
||
},
|
||
},
|
||
expected: fleet.OperatingSystem{
|
||
Name: "macOS",
|
||
Version: "12.5.0",
|
||
Arch: "x86_64",
|
||
KernelVersion: "21.6.0",
|
||
},
|
||
},
|
||
// macOS with Rapid Security Response version
|
||
{
|
||
data: []map[string]string{
|
||
{
|
||
"name": "macOS",
|
||
"version": "13.4.1",
|
||
"major": "13",
|
||
"minor": "4",
|
||
"patch": "1",
|
||
"build": "22F82",
|
||
"arch": "arm64",
|
||
"kernel_version": "21.6.0",
|
||
"extra": "(c) ",
|
||
},
|
||
},
|
||
expected: fleet.OperatingSystem{
|
||
Name: "macOS",
|
||
Version: "13.4.1 (c)",
|
||
Arch: "arm64",
|
||
KernelVersion: "21.6.0",
|
||
},
|
||
},
|
||
{
|
||
data: []map[string]string{
|
||
{
|
||
"name": "Ubuntu",
|
||
"version": "20.04.2 LTS (Focal Fossa)",
|
||
"major": "20",
|
||
"minor": "4",
|
||
"patch": "0",
|
||
"build": "",
|
||
"arch": "x86_64",
|
||
"kernel_version": "5.10.76-linuxkit",
|
||
},
|
||
},
|
||
expected: fleet.OperatingSystem{
|
||
Name: "Ubuntu",
|
||
Version: "20.04.2 LTS",
|
||
Arch: "x86_64",
|
||
KernelVersion: "5.10.76-linuxkit",
|
||
},
|
||
},
|
||
{
|
||
data: []map[string]string{
|
||
{
|
||
"name": "CentOS Linux",
|
||
"version": "CentOS Linux release 7.9.2009 (Core)",
|
||
"major": "7",
|
||
"minor": "9",
|
||
"patch": "2009",
|
||
"build": "",
|
||
"arch": "x86_64",
|
||
"kernel_version": "5.10.76-linuxkit",
|
||
},
|
||
},
|
||
expected: fleet.OperatingSystem{
|
||
Name: "CentOS Linux",
|
||
Version: "7.9.2009",
|
||
Arch: "x86_64",
|
||
KernelVersion: "5.10.76-linuxkit",
|
||
},
|
||
},
|
||
{
|
||
data: []map[string]string{
|
||
{
|
||
"name": "Debian GNU/Linux",
|
||
"version": "10 (buster)",
|
||
"major": "10",
|
||
"minor": "0",
|
||
"patch": "0",
|
||
"build": "",
|
||
"arch": "x86_64",
|
||
"kernel_version": "5.10.76-linuxkit",
|
||
},
|
||
},
|
||
expected: fleet.OperatingSystem{
|
||
Name: "Debian GNU/Linux",
|
||
Version: "10.0.0",
|
||
Arch: "x86_64",
|
||
KernelVersion: "5.10.76-linuxkit",
|
||
},
|
||
},
|
||
{
|
||
data: []map[string]string{
|
||
{
|
||
"name": "CentOS Linux",
|
||
"version": "CentOS Linux release 7.9.2009 (Core)",
|
||
"major": "7",
|
||
"minor": "9",
|
||
"patch": "2009",
|
||
"build": "",
|
||
"arch": "x86_64",
|
||
"kernel_version": "5.10.76-linuxkit",
|
||
},
|
||
},
|
||
expected: fleet.OperatingSystem{
|
||
Name: "CentOS Linux",
|
||
Version: "7.9.2009",
|
||
Arch: "x86_64",
|
||
KernelVersion: "5.10.76-linuxkit",
|
||
},
|
||
},
|
||
{
|
||
data: []map[string]string{
|
||
{
|
||
"name": "Arch Linux ARM",
|
||
"version": "",
|
||
"major": "1",
|
||
"minor": "2",
|
||
"patch": "3",
|
||
"build": "",
|
||
"arch": "aarch64",
|
||
"kernel_version": "6.6.10-1-ARCH",
|
||
},
|
||
},
|
||
expected: fleet.OperatingSystem{
|
||
Name: "Arch Linux",
|
||
Version: "1.2.3",
|
||
Arch: "aarch64",
|
||
KernelVersion: "6.6.10-1-ARCH",
|
||
},
|
||
},
|
||
{
|
||
data: []map[string]string{
|
||
{
|
||
"name": "Arch Linux",
|
||
"version": "",
|
||
"major": "1",
|
||
"minor": "2",
|
||
"patch": "3",
|
||
"build": "",
|
||
"arch": "x86_64",
|
||
"kernel_version": "6.6.10-1-ARCH",
|
||
},
|
||
},
|
||
expected: fleet.OperatingSystem{
|
||
Name: "Arch Linux",
|
||
Version: "1.2.3",
|
||
Arch: "x86_64",
|
||
KernelVersion: "6.6.10-1-ARCH",
|
||
},
|
||
},
|
||
} {
|
||
t.Run(tc.expected.Name, func(t *testing.T) {
|
||
ds.UpdateHostOperatingSystemFunc = func(ctx context.Context, hostID uint, hostOS fleet.OperatingSystem) error {
|
||
require.Equal(t, uint(i), hostID) //nolint:gosec // dismiss G115
|
||
require.Equal(t, tc.expected, hostOS)
|
||
return nil
|
||
}
|
||
|
||
err := directIngestOSUnixLike(t.Context(), slog.New(slog.DiscardHandler), &fleet.Host{ID: uint(i)}, //nolint:gosec // dismiss G115
|
||
ds, tc.data)
|
||
|
||
require.NoError(t, err)
|
||
require.True(t, ds.UpdateHostOperatingSystemFuncInvoked)
|
||
ds.UpdateHostOperatingSystemFuncInvoked = false
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestAppConfigReplaceQuery(t *testing.T) {
|
||
queries := GetDetailQueries(t.Context(), config.FleetConfig{}, nil, &fleet.Features{EnableHostUsers: true}, Integrations{}, nil)
|
||
originalQuery := queries["users"].Query
|
||
|
||
replacementMap := make(map[string]*string)
|
||
replacementMap["users"] = ptr.String("select 1 from blah")
|
||
queries = GetDetailQueries(t.Context(), config.FleetConfig{}, nil, &fleet.Features{EnableHostUsers: true, DetailQueryOverrides: replacementMap}, Integrations{}, nil)
|
||
assert.NotEqual(t, originalQuery, queries["users"].Query)
|
||
assert.Equal(t, "select 1 from blah", queries["users"].Query)
|
||
|
||
replacementMap["users"] = nil
|
||
queries = GetDetailQueries(t.Context(), config.FleetConfig{}, nil, &fleet.Features{EnableHostUsers: true, DetailQueryOverrides: replacementMap}, Integrations{}, nil)
|
||
_, exists := queries["users"]
|
||
assert.False(t, exists)
|
||
|
||
// put the query back again
|
||
replacementMap["users"] = ptr.String("select 1 from blah")
|
||
queries = GetDetailQueries(t.Context(), config.FleetConfig{}, nil, &fleet.Features{EnableHostUsers: true, DetailQueryOverrides: replacementMap}, Integrations{}, nil)
|
||
assert.NotEqual(t, originalQuery, queries["users"].Query)
|
||
assert.Equal(t, "select 1 from blah", queries["users"].Query)
|
||
|
||
// empty strings are also ignored
|
||
replacementMap["users"] = ptr.String("")
|
||
queries = GetDetailQueries(t.Context(), config.FleetConfig{}, nil, &fleet.Features{EnableHostUsers: true, DetailQueryOverrides: replacementMap}, Integrations{}, nil)
|
||
_, exists = queries["users"]
|
||
assert.False(t, exists)
|
||
}
|
||
|
||
func TestDirectIngestSoftware(t *testing.T) {
|
||
ds := new(mock.Store)
|
||
ctx := t.Context()
|
||
logger := slog.New(slog.DiscardHandler)
|
||
host := fleet.Host{ID: uint(1)}
|
||
|
||
t.Run("ingesting installed software path", func(t *testing.T) {
|
||
data := []map[string]string{
|
||
{
|
||
"name": "Software 1",
|
||
"version": "12.5.0",
|
||
"source": "apps",
|
||
"bundle_identifier": "com.bundle.com",
|
||
"vendor": "EvilCorp",
|
||
"installed_path": "",
|
||
},
|
||
{
|
||
"name": "Software 2",
|
||
"version": "0.0.1",
|
||
"source": "apps",
|
||
"bundle_identifier": "coms.widgets.com",
|
||
"vendor": "widgets",
|
||
"installed_path": "/tmp/some_path",
|
||
},
|
||
}
|
||
|
||
ds.UpdateHostSoftwareFunc = func(ctx context.Context, hostID uint, software []fleet.Software) (*fleet.UpdateHostSoftwareDBResult, error) {
|
||
return nil, nil
|
||
}
|
||
|
||
t.Run("errors are reported back", func(t *testing.T) {
|
||
ds.UpdateHostSoftwareInstalledPathsFunc = func(ctx context.Context, hostID uint, sPaths map[string]struct{}, result *fleet.UpdateHostSoftwareDBResult) error {
|
||
return errors.New("some error")
|
||
}
|
||
require.Error(t, directIngestSoftware(ctx, logger, &host, ds, data), "some error")
|
||
ds.UpdateHostSoftwareInstalledPathsFuncInvoked = false
|
||
})
|
||
|
||
t.Run("only entries with installed_path set are persisted", func(t *testing.T) {
|
||
var calledWith map[string]struct{}
|
||
ds.UpdateHostSoftwareInstalledPathsFunc = func(ctx context.Context, hostID uint, sPaths map[string]struct{}, result *fleet.UpdateHostSoftwareDBResult) error {
|
||
calledWith = make(map[string]struct{})
|
||
for k, v := range sPaths {
|
||
calledWith[k] = v
|
||
}
|
||
return nil
|
||
}
|
||
|
||
require.NoError(t, directIngestSoftware(ctx, logger, &host, ds, data))
|
||
require.True(t, ds.UpdateHostSoftwareFuncInvoked)
|
||
|
||
require.Len(t, calledWith, 1)
|
||
require.Contains(t, strings.Join(maps.Keys(calledWith), " "), fmt.Sprintf("%s%s%s%s%s%s%s%s%s%s%s", data[1]["installed_path"], fleet.SoftwareFieldSeparator, "", fleet.SoftwareFieldSeparator, "", fleet.SoftwareFieldSeparator, "", fleet.SoftwareFieldSeparator, "", fleet.SoftwareFieldSeparator, data[1]["name"]))
|
||
|
||
ds.UpdateHostSoftwareInstalledPathsFuncInvoked = false
|
||
})
|
||
})
|
||
|
||
t.Run("vendor gets truncated", func(t *testing.T) {
|
||
for _, tc := range []struct {
|
||
data []map[string]string
|
||
expected string
|
||
}{
|
||
{
|
||
data: []map[string]string{
|
||
{
|
||
"name": "Software 1",
|
||
"version": "12.5",
|
||
"source": "My backyard",
|
||
"bundle_identifier": "",
|
||
"vendor": "Fleet",
|
||
},
|
||
},
|
||
expected: "Fleet",
|
||
},
|
||
{
|
||
data: []map[string]string{
|
||
{
|
||
"name": "Software 1",
|
||
"version": "12.5",
|
||
"source": "My backyard",
|
||
"bundle_identifier": "",
|
||
"vendor": `oFZTwTV5WxJt02EVHEBcnhLzuJ8wnxKwfbabPWy7yTSiQbabEcAGDVmoXKZEZJLWObGD0cVfYptInHYgKjtDeDsBh2a8669EnyAqyBECXbFjSh1111`,
|
||
},
|
||
},
|
||
expected: `oFZTwTV5WxJt02EVHEBcnhLzuJ8wnxKwfbabPWy7yTSiQbabEcAGDVmoXKZEZJLWObGD0cVfYptInHYgKjtDeDsBh2a8669EnyAqyBECXbFjSh1...`,
|
||
},
|
||
} {
|
||
ds.UpdateHostSoftwareFunc = func(ctx context.Context, hostID uint, software []fleet.Software) (*fleet.UpdateHostSoftwareDBResult, error) {
|
||
require.Len(t, software, 1)
|
||
require.Equal(t, tc.expected, software[0].Vendor)
|
||
return nil, nil
|
||
}
|
||
|
||
ds.UpdateHostSoftwareInstalledPathsFunc = func(ctx context.Context, hostID uint, sPaths map[string]struct{}, result *fleet.UpdateHostSoftwareDBResult) error {
|
||
// NOP - This functionality is tested elsewhere
|
||
return nil
|
||
}
|
||
|
||
require.NoError(t, directIngestSoftware(ctx, logger, &host, ds, tc.data))
|
||
require.True(t, ds.UpdateHostSoftwareFuncInvoked)
|
||
ds.UpdateHostSoftwareFuncInvoked = false
|
||
}
|
||
})
|
||
|
||
t.Run("cdhash_sha256", func(t *testing.T) {
|
||
data := []map[string]string{
|
||
{
|
||
"name": "Software 1",
|
||
"version": "12.5.0",
|
||
"source": "apps",
|
||
"bundle_identifier": "com.bundle.com",
|
||
"vendor": "EvilCorp",
|
||
|
||
"installed_path": "/Applications/Software1.app",
|
||
"team_identifier": "corp1",
|
||
"cdhash_sha256": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
|
||
"executable_sha256": "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
|
||
"executable_path": "/Applications/Software1.app/Contents/MacOS/Software1",
|
||
},
|
||
{
|
||
"name": "Software 2",
|
||
"version": "0.0.1",
|
||
"source": "apps",
|
||
"bundle_identifier": "coms.widgets.com",
|
||
"vendor": "widgets",
|
||
|
||
"team_identifier": "corp2",
|
||
"installed_path": "/Applications/Software2.app",
|
||
},
|
||
}
|
||
var dataAsSoftware []fleet.Software
|
||
for _, entry := range data {
|
||
software := fleet.Software{
|
||
Name: entry["name"],
|
||
Version: entry["version"],
|
||
Source: entry["source"],
|
||
BundleIdentifier: entry["bundle_identifier"],
|
||
Vendor: entry["vendor"],
|
||
}
|
||
dataAsSoftware = append(dataAsSoftware, software)
|
||
}
|
||
|
||
ds.UpdateHostSoftwareFunc = func(ctx context.Context, hostID uint, software []fleet.Software) (*fleet.UpdateHostSoftwareDBResult, error) {
|
||
return nil, nil
|
||
}
|
||
ds.UpdateHostSoftwareInstalledPathsFunc = func(ctx context.Context, hostID uint, sPaths map[string]struct{}, result *fleet.UpdateHostSoftwareDBResult) error {
|
||
require.Len(t, sPaths, 2)
|
||
require.Contains(t, sPaths,
|
||
fmt.Sprintf(
|
||
"%s%s%s%s%s%s%s%s%s%s%s",
|
||
data[0]["installed_path"],
|
||
fleet.SoftwareFieldSeparator,
|
||
data[0]["team_identifier"],
|
||
fleet.SoftwareFieldSeparator,
|
||
data[0]["cdhash_sha256"],
|
||
fleet.SoftwareFieldSeparator,
|
||
data[0]["executable_sha256"],
|
||
fleet.SoftwareFieldSeparator,
|
||
data[0]["executable_path"],
|
||
fleet.SoftwareFieldSeparator,
|
||
dataAsSoftware[0].ToUniqueStr(),
|
||
),
|
||
)
|
||
require.Contains(t, sPaths,
|
||
fmt.Sprintf(
|
||
"%s%s%s%s%s%s%s%s%s%s%s",
|
||
data[1]["installed_path"],
|
||
fleet.SoftwareFieldSeparator,
|
||
data[1]["team_identifier"],
|
||
fleet.SoftwareFieldSeparator,
|
||
"",
|
||
fleet.SoftwareFieldSeparator,
|
||
"",
|
||
fleet.SoftwareFieldSeparator,
|
||
"",
|
||
fleet.SoftwareFieldSeparator,
|
||
dataAsSoftware[1].ToUniqueStr(),
|
||
),
|
||
)
|
||
return nil
|
||
}
|
||
|
||
require.NoError(t, directIngestSoftware(ctx, logger, &host, ds, data))
|
||
require.True(t, ds.UpdateHostSoftwareInstalledPathsFuncInvoked)
|
||
ds.UpdateHostSoftwareInstalledPathsFuncInvoked = false
|
||
})
|
||
|
||
t.Run("all software columns are copied properly", func(t *testing.T) {
|
||
data := []map[string]string{
|
||
{
|
||
"name": "Test Software",
|
||
"version": "1.2.3",
|
||
"source": "chrome_extensions",
|
||
"vendor": "Test Vendor",
|
||
"installed_path": "/Applications/TestSoftware.app",
|
||
"release": "1.0",
|
||
"arch": "x86_64",
|
||
"bundle_identifier": "com.test.software",
|
||
"extension_id": "ext123",
|
||
"extension_for": "chrome",
|
||
"last_opened_at": "1672574400",
|
||
},
|
||
}
|
||
|
||
var capturedSoftware []fleet.Software
|
||
ds.UpdateHostSoftwareFunc = func(ctx context.Context, hostID uint, software []fleet.Software) (*fleet.UpdateHostSoftwareDBResult, error) {
|
||
capturedSoftware = make([]fleet.Software, len(software))
|
||
copy(capturedSoftware, software)
|
||
return nil, nil
|
||
}
|
||
|
||
ds.UpdateHostSoftwareInstalledPathsFunc = func(ctx context.Context, hostID uint, sPaths map[string]struct{}, result *fleet.UpdateHostSoftwareDBResult) error {
|
||
return nil
|
||
}
|
||
|
||
require.NoError(t, directIngestSoftware(ctx, logger, &host, ds, data))
|
||
require.True(t, ds.UpdateHostSoftwareFuncInvoked)
|
||
require.Len(t, capturedSoftware, 1)
|
||
|
||
software := capturedSoftware[0]
|
||
|
||
// Verify all columns from lines 1838-1848 are properly copied
|
||
assert.Equal(t, "Test Software", software.Name)
|
||
assert.Equal(t, "1.2.3", software.Version)
|
||
assert.Equal(t, "chrome_extensions", software.Source)
|
||
assert.Equal(t, "Test Vendor", software.Vendor)
|
||
assert.Equal(t, "1.0", software.Release)
|
||
assert.Equal(t, "x86_64", software.Arch)
|
||
assert.Equal(t, "com.test.software", software.BundleIdentifier)
|
||
assert.Equal(t, "ext123", software.ExtensionID)
|
||
assert.Equal(t, "chrome", software.ExtensionFor)
|
||
|
||
// Verify last_opened_at is properly parsed and set
|
||
require.NotNil(t, software.LastOpenedAt)
|
||
expectedTime := time.Unix(1672574400, 0).UTC()
|
||
assert.True(t, software.LastOpenedAt.Equal(expectedTime))
|
||
|
||
ds.UpdateHostSoftwareFuncInvoked = false
|
||
})
|
||
}
|
||
|
||
func TestDirectIngestWindowsUpdateHistory(t *testing.T) {
|
||
ds := new(mock.Store)
|
||
ds.InsertWindowsUpdatesFunc = func(ctx context.Context, hostID uint, updates []fleet.WindowsUpdate) error {
|
||
require.Len(t, updates, 6)
|
||
require.ElementsMatch(t, []fleet.WindowsUpdate{
|
||
{KBID: 2267602, DateEpoch: 1657929207},
|
||
{KBID: 890830, DateEpoch: 1658226954},
|
||
{KBID: 5013887, DateEpoch: 1658225364},
|
||
{KBID: 5005463, DateEpoch: 1658225225},
|
||
{KBID: 5010472, DateEpoch: 1658224963},
|
||
{KBID: 4052623, DateEpoch: 1657929544},
|
||
}, updates)
|
||
return nil
|
||
}
|
||
|
||
host := fleet.Host{
|
||
ID: 1,
|
||
}
|
||
|
||
payload := []map[string]string{
|
||
{"date": "1659392951", "title": "Security Intelligence Update for Microsoft Defender Antivirus - KB2267602 (Version 1.371.1239.0)"},
|
||
{"date": "1658271402", "title": "Security Intelligence Update for Microsoft Defender Antivirus - KB2267602 (Version 1.371.442.0)"},
|
||
{"date": "1658228495", "title": "Security Intelligence Update for Microsoft Defender Antivirus - KB2267602 (Version 1.371.415.0)"},
|
||
{"date": "1658226954", "title": "Windows Malicious Software Removal Tool x64 - v5.103 (KB890830)"},
|
||
{"date": "1658225364", "title": "2022-06 Cumulative Update for .NET Framework 3.5 and 4.8 for Windows 10 Version 21H2 for x64 (KB5013887)"},
|
||
{"date": "1658225225", "title": "2022-04 Update for Windows 10 Version 21H2 for x64-based Systems (KB5005463)"},
|
||
{"date": "1658224963", "title": "2022-02 Cumulative Update Preview for .NET Framework 3.5 and 4.8 for Windows 10 Version 21H2 for x64 (KB5010472)"},
|
||
{"date": "1658222131", "title": "Security Intelligence Update for Microsoft Defender Antivirus - KB2267602 (Version 1.371.400.0)"},
|
||
{"date": "1658189063", "title": "Security Intelligence Update for Microsoft Defender Antivirus - KB2267602 (Version 1.371.376.0)"},
|
||
{"date": "1658185542", "title": "Security Intelligence Update for Microsoft Defender Antivirus - KB2267602 (Version 1.371.386.0)"},
|
||
{"date": "1657929544", "title": "Update for Microsoft Defender Antivirus antimalware platform - KB4052623 (Version 4.18.2205.7)"},
|
||
{"date": "1657929207", "title": "Security Intelligence Update for Microsoft Defender Antivirus - KB2267602 (Version 1.371.203.0)"},
|
||
}
|
||
|
||
err := directIngestWindowsUpdateHistory(t.Context(), slog.New(slog.DiscardHandler), &host, ds, payload)
|
||
require.NoError(t, err)
|
||
require.True(t, ds.InsertWindowsUpdatesFuncInvoked)
|
||
}
|
||
|
||
func TestIngestKubequeryInfo(t *testing.T) {
|
||
err := ingestKubequeryInfo(t.Context(), slog.New(slog.DiscardHandler), &fleet.Host{}, nil)
|
||
require.Error(t, err)
|
||
err = ingestKubequeryInfo(t.Context(), slog.New(slog.DiscardHandler), &fleet.Host{}, []map[string]string{})
|
||
require.Error(t, err)
|
||
err = ingestKubequeryInfo(t.Context(), slog.New(slog.DiscardHandler), &fleet.Host{}, []map[string]string{
|
||
{
|
||
"cluster_name": "foo",
|
||
},
|
||
})
|
||
require.NoError(t, err)
|
||
}
|
||
|
||
func TestDirectDiskEncryption(t *testing.T) {
|
||
ds := new(mock.Store)
|
||
var expectEncrypted bool
|
||
ds.SetOrUpdateHostDisksEncryptionFunc = func(ctx context.Context, id uint, encrypted bool) error {
|
||
assert.Equal(t, expectEncrypted, encrypted)
|
||
return nil
|
||
}
|
||
|
||
host := fleet.Host{
|
||
ID: 1,
|
||
}
|
||
|
||
// set to true (osquery returned a row)
|
||
expectEncrypted = true
|
||
err := directIngestDiskEncryption(t.Context(), slog.New(slog.DiscardHandler), &host, ds, []map[string]string{
|
||
{"col1": "1"},
|
||
})
|
||
require.NoError(t, err)
|
||
require.True(t, ds.SetOrUpdateHostDisksEncryptionFuncInvoked)
|
||
ds.SetOrUpdateHostDisksEncryptionFuncInvoked = false
|
||
|
||
// set to false (osquery returned nothing)
|
||
expectEncrypted = false
|
||
err = directIngestDiskEncryption(t.Context(), slog.New(slog.DiscardHandler), &host, ds, []map[string]string{})
|
||
require.NoError(t, err)
|
||
require.True(t, ds.SetOrUpdateHostDisksEncryptionFuncInvoked)
|
||
ds.SetOrUpdateHostDisksEncryptionFuncInvoked = false
|
||
}
|
||
|
||
func TestDirectIngestDiskEncryptionLinux(t *testing.T) {
|
||
ds := new(mock.Store)
|
||
var expectEncrypted bool
|
||
ds.SetOrUpdateHostDisksEncryptionFunc = func(ctx context.Context, id uint, encrypted bool) error {
|
||
assert.Equal(t, expectEncrypted, encrypted)
|
||
return nil
|
||
}
|
||
host := fleet.Host{
|
||
ID: 1,
|
||
}
|
||
|
||
expectEncrypted = false
|
||
err := directIngestDiskEncryptionLinux(t.Context(), slog.New(slog.DiscardHandler), &host, ds, []map[string]string{})
|
||
require.NoError(t, err)
|
||
require.True(t, ds.SetOrUpdateHostDisksEncryptionFuncInvoked)
|
||
ds.SetOrUpdateHostDisksEncryptionFuncInvoked = false
|
||
|
||
expectEncrypted = true
|
||
err = directIngestDiskEncryptionLinux(t.Context(), slog.New(slog.DiscardHandler), &host, ds, []map[string]string{
|
||
{"path": "/etc/hosts", "encrypted": "0"},
|
||
{"path": "/tmp", "encrypted": "0"},
|
||
{"path": "/", "encrypted": "1"},
|
||
})
|
||
require.NoError(t, err)
|
||
require.True(t, ds.SetOrUpdateHostDisksEncryptionFuncInvoked)
|
||
}
|
||
|
||
func TestDirectIngestDiskEncryptionKeyDarwin(t *testing.T) {
|
||
ds := new(mock.Store)
|
||
ctx := t.Context()
|
||
logger := slog.New(slog.DiscardHandler)
|
||
host := &fleet.Host{ID: 1}
|
||
|
||
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
||
return &fleet.AppConfig{
|
||
MDM: fleet.MDM{
|
||
EnableDiskEncryption: optjson.SetBool(true),
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
var wantKey string
|
||
|
||
mockFileLines := func(wantKey string, wantEncrypted string) []map[string]string {
|
||
var output []map[string]string
|
||
scanner := bufio.NewScanner(bytes.NewBuffer([]byte(wantKey)))
|
||
scanner.Split(bufio.ScanLines)
|
||
|
||
for scanner.Scan() {
|
||
line := scanner.Text()
|
||
item := make(map[string]string)
|
||
item["hex_line"] = hex.EncodeToString([]byte(line))
|
||
item["encrypted"] = wantEncrypted
|
||
output = append(output, item)
|
||
}
|
||
return output
|
||
}
|
||
|
||
mockFilevaultPRK := func(wantKey string, wantEncrypted string) []map[string]string {
|
||
return []map[string]string{
|
||
{"filevault_key": base64.StdEncoding.EncodeToString([]byte(wantKey)), "encrypted": wantEncrypted},
|
||
}
|
||
}
|
||
|
||
ds.SetOrUpdateHostDiskEncryptionKeyFunc = func(ctx context.Context, incomingHost *fleet.Host, encryptedBase64Key, clientError string,
|
||
decryptable *bool,
|
||
) (bool, error) {
|
||
if base64.StdEncoding.EncodeToString([]byte(wantKey)) != encryptedBase64Key {
|
||
return false, errors.New("key mismatch")
|
||
}
|
||
if host.ID != incomingHost.ID {
|
||
return false, errors.New("host ID mismatch")
|
||
}
|
||
if encryptedBase64Key == "" && (decryptable == nil || *decryptable == true) {
|
||
return false, errors.New("decryptable should be false if the key is empty")
|
||
}
|
||
return false, nil
|
||
}
|
||
|
||
t.Run("empty key", func(t *testing.T) {
|
||
err := directIngestDiskEncryptionKeyFileLinesDarwin(ctx, logger, host, ds, []map[string]string{})
|
||
require.NoError(t, err)
|
||
require.False(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
|
||
|
||
err = directIngestDiskEncryptionKeyFileDarwin(ctx, logger, host, ds, []map[string]string{})
|
||
require.NoError(t, err)
|
||
require.False(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
|
||
|
||
err = directIngestDiskEncryptionKeyFileLinesDarwin(ctx, logger, host, ds, []map[string]string{{"encrypted": "0"}})
|
||
require.NoError(t, err)
|
||
require.False(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
|
||
|
||
err = directIngestDiskEncryptionKeyFileDarwin(ctx, logger, host, ds, []map[string]string{{"encrypted": "0"}})
|
||
require.NoError(t, err)
|
||
require.False(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
|
||
|
||
err = directIngestDiskEncryptionKeyFileLinesDarwin(ctx, logger, host, ds, []map[string]string{{"encrypted": "1"}})
|
||
require.NoError(t, err)
|
||
require.True(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
|
||
ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked = false
|
||
|
||
err = directIngestDiskEncryptionKeyFileDarwin(ctx, logger, host, ds, []map[string]string{{"encrypted": "1"}})
|
||
require.NoError(t, err)
|
||
require.True(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
|
||
ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked = false
|
||
|
||
err = directIngestDiskEncryptionKeyFileLinesDarwin(ctx, logger, host, ds, []map[string]string{{"encrypted": "1", "hex_line": ""}})
|
||
require.NoError(t, err)
|
||
require.True(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
|
||
ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked = false
|
||
|
||
err = directIngestDiskEncryptionKeyFileDarwin(ctx, logger, host, ds, []map[string]string{{"encrypted": "1", "filevault_key": ""}})
|
||
require.NoError(t, err)
|
||
require.True(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
|
||
ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked = false
|
||
})
|
||
|
||
t.Run("key contains new lines and carriage return", func(t *testing.T) {
|
||
wantKey = "This is only a \n\r\n\n test."
|
||
|
||
err := directIngestDiskEncryptionKeyFileLinesDarwin(ctx, logger, host, ds, mockFileLines(wantKey, "1"))
|
||
// it is a known limitation with the current file_lines implementation that causes this to fail
|
||
// because it relies on bufio.ScanLines, which drops "\r" from "\r\n"
|
||
require.ErrorContains(t, err, "key mismatch")
|
||
require.True(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
|
||
ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked = false
|
||
|
||
err = directIngestDiskEncryptionKeyFileDarwin(ctx, logger, host, ds, mockFilevaultPRK(wantKey, "1"))
|
||
// filevault_prk does not have the scan lines limitation
|
||
require.NoError(t, err)
|
||
require.True(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
|
||
ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked = false
|
||
})
|
||
|
||
t.Run("key contains new lines", func(t *testing.T) {
|
||
wantKey = "This is only a \n\n\n test."
|
||
|
||
err := directIngestDiskEncryptionKeyFileLinesDarwin(ctx, logger, host, ds, mockFileLines(wantKey, "1"))
|
||
// new lines are not a problem if they are not preceded by carriage return
|
||
require.NoError(t, err)
|
||
require.True(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
|
||
ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked = false
|
||
|
||
err = directIngestDiskEncryptionKeyFileDarwin(ctx, logger, host, ds, mockFilevaultPRK(wantKey, "1"))
|
||
// filevault_prk does not have the scan lines limitation
|
||
require.NoError(t, err)
|
||
require.True(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
|
||
ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked = false
|
||
})
|
||
}
|
||
|
||
func TestDirectIngestHostMacOSProfiles(t *testing.T) {
|
||
ds := new(mock.Store)
|
||
ctx := t.Context()
|
||
logger := slog.New(slog.DiscardHandler)
|
||
h := &fleet.Host{ID: 1}
|
||
|
||
toRows := func(profs []*fleet.HostMacOSProfile) []map[string]string {
|
||
rows := make([]map[string]string, len(profs))
|
||
for i, p := range profs {
|
||
rows[i] = map[string]string{
|
||
"identifier": p.Identifier,
|
||
"display_name": p.DisplayName,
|
||
"install_date": p.InstallDate.Format("2006-01-02 15:04:05 -0700"),
|
||
}
|
||
}
|
||
return rows
|
||
}
|
||
|
||
var installedProfiles []*fleet.HostMacOSProfile
|
||
ds.GetHostMDMProfilesExpectedForVerificationFunc = func(ctx context.Context, host *fleet.Host) (map[string]*fleet.ExpectedMDMProfile, error) {
|
||
require.Equal(t, h.ID, host.ID)
|
||
expected := make(map[string]*fleet.ExpectedMDMProfile, len(installedProfiles))
|
||
for _, p := range installedProfiles {
|
||
expected[p.Identifier] = &fleet.ExpectedMDMProfile{
|
||
Identifier: p.Identifier,
|
||
EarliestInstallDate: p.InstallDate,
|
||
}
|
||
}
|
||
return expected, nil
|
||
}
|
||
ds.UpdateHostMDMProfilesVerificationFunc = func(ctx context.Context, host *fleet.Host, toVerify, toFailed, toRetry []string) error {
|
||
require.Equal(t, h.UUID, host.UUID)
|
||
require.Equal(t, len(installedProfiles), len(toVerify))
|
||
require.Len(t, toFailed, 0)
|
||
require.Len(t, toRetry, 0)
|
||
for _, p := range installedProfiles {
|
||
require.Contains(t, toVerify, p.Identifier)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// expect no error: happy path
|
||
installedProfiles = []*fleet.HostMacOSProfile{
|
||
{
|
||
Identifier: "com.example.test",
|
||
DisplayName: "Test Profile",
|
||
InstallDate: time.Now().Truncate(time.Second),
|
||
},
|
||
}
|
||
rows := toRows(installedProfiles)
|
||
require.NoError(t, directIngestMacOSProfiles(ctx, logger, h, ds, rows))
|
||
|
||
// expect no error: identifer or display name is empty
|
||
installedProfiles = append(installedProfiles, &fleet.HostMacOSProfile{
|
||
Identifier: "",
|
||
DisplayName: "",
|
||
InstallDate: time.Now().Truncate(time.Second),
|
||
})
|
||
rows = toRows(installedProfiles)
|
||
require.NoError(t, directIngestMacOSProfiles(ctx, logger, h, ds, rows))
|
||
|
||
// expect no error: empty rows
|
||
require.NoError(t, directIngestMacOSProfiles(ctx, logger, h, ds, []map[string]string{}))
|
||
|
||
// expect error: install date format is not "2006-01-02 15:04:05 -0700"
|
||
rows[0]["install_date"] = time.Now().Format(time.UnixDate)
|
||
require.ErrorContains(t, directIngestMacOSProfiles(ctx, logger, h, ds, rows), "parsing time")
|
||
}
|
||
|
||
func TestDirectIngestMDMDeviceIDWindows(t *testing.T) {
|
||
ds := new(mock.Store)
|
||
ctx := t.Context()
|
||
logger := slog.New(slog.DiscardHandler)
|
||
host := &fleet.Host{ID: 1, UUID: "mdm-windows-hw-uuid"}
|
||
|
||
returnEnrollmentsUpdated := true
|
||
ds.UpdateMDMWindowsEnrollmentsHostUUIDFunc = func(ctx context.Context, hostUUID string, deviceID string) (bool, error) {
|
||
require.NotEmpty(t, deviceID)
|
||
require.Equal(t, host.UUID, hostUUID)
|
||
return returnEnrollmentsUpdated, nil
|
||
}
|
||
ds.UpdateMDMInstalledFromDEPFunc = func(ctx context.Context, hostID uint, enrolledFromDEP bool) error {
|
||
return nil
|
||
}
|
||
|
||
baseEnrolledDeviceToReturn := fleet.MDMWindowsEnrolledDevice{
|
||
ID: 1,
|
||
MDMHardwareID: "12345",
|
||
MDMDeviceState: "MDMDeviceEnrolledEnrolled",
|
||
MDMDeviceType: "CIMClient_Windows",
|
||
MDMDeviceName: "Fleetie-PC",
|
||
MDMEnrollUserID: "a1b2c3d4e5f6g7h8i9j0",
|
||
MDMEnrollProtoVersion: "7.0",
|
||
MDMEnrollClientVersion: "10.0.26200.7020",
|
||
MDMEnrollType: "Full",
|
||
}
|
||
ds.MDMWindowsGetEnrolledDeviceWithDeviceIDFunc = func(ctx context.Context, deviceID string) (*fleet.MDMWindowsEnrolledDevice, error) {
|
||
device := baseEnrolledDeviceToReturn
|
||
device.MDMDeviceID = deviceID
|
||
return &device, nil
|
||
}
|
||
|
||
ds.ReplaceHostDeviceMappingFunc = func(ctx context.Context, hostID uint, mappings []*fleet.HostDeviceMapping, source string) error {
|
||
require.Len(t, mappings, 1)
|
||
require.Equal(t, baseEnrolledDeviceToReturn.MDMEnrollUserID, mappings[0].Email)
|
||
require.Equal(t, host.ID, mappings[0].HostID)
|
||
require.Equal(t, fleet.DeviceMappingMDMIdpAccounts, mappings[0].Source)
|
||
require.Equal(t, fleet.DeviceMappingMDMIdpAccounts, source)
|
||
return nil
|
||
}
|
||
|
||
baseSCIMUser := fleet.ScimUser{
|
||
ID: 1,
|
||
UserName: "fleetie@example.com",
|
||
GivenName: ptr.String("Fleetie"),
|
||
FamilyName: ptr.String("McDougal"),
|
||
Emails: []fleet.ScimUserEmail{{ScimUserID: 1, Email: "fleetie@example.com"}},
|
||
}
|
||
returnSCIMUser := true
|
||
|
||
ds.ScimUserByUserNameOrEmailFunc = func(ctx context.Context, name string, email string) (*fleet.ScimUser, error) {
|
||
if returnSCIMUser {
|
||
return &baseSCIMUser, nil
|
||
}
|
||
return nil, common_mysql.NotFound("SCIMUser")
|
||
}
|
||
|
||
ds.SetOrUpdateHostSCIMUserMappingFunc = func(ctx context.Context, hostID uint, scimUserID uint) error {
|
||
require.Equal(t, host.ID, hostID)
|
||
require.Equal(t, baseSCIMUser.ID, scimUserID)
|
||
return nil
|
||
}
|
||
|
||
ds.DeleteHostSCIMUserMappingFunc = func(ctx context.Context, hostID uint) error {
|
||
require.Equal(t, host.ID, hostID)
|
||
return nil
|
||
}
|
||
|
||
testCases := []struct {
|
||
name string
|
||
rows []map[string]string
|
||
expectError string
|
||
mdmEnrollUserID string
|
||
mdmEnrollNotInOOBE bool
|
||
returnSCIMUser bool
|
||
expectUpdateMDMWindowsEnrollmentsHostUUIDFuncInvoked bool
|
||
expectUpdateMDMInstalledFromDEPFuncInvoked bool
|
||
expectReplaceHostDeviceMappingFuncInvoked bool
|
||
expectScimUserByUserNameOrEmailFuncInvoked bool
|
||
expectDeleteHostSCIMUserMappingFuncInvoked bool
|
||
}{
|
||
{
|
||
// if no rows, assume the registry key is not present (i.e. mdm is turned off) and do nothing
|
||
name: "no rows",
|
||
rows: []map[string]string{},
|
||
},
|
||
{
|
||
name: "multiple rows",
|
||
rows: []map[string]string{
|
||
{"name": "mdm-windows-hostname", "data": "mdm-windows-device-id"},
|
||
{"name": "mdm-windows-hostname2", "data": "mdm-windows-device-id2"},
|
||
}, expectError: "invalid number of rows",
|
||
},
|
||
{
|
||
name: "happy path, device without UPN",
|
||
rows: []map[string]string{
|
||
{"name": "mdm-windows-hostname", "data": "mdm-windows-device-id"},
|
||
},
|
||
expectUpdateMDMWindowsEnrollmentsHostUUIDFuncInvoked: true,
|
||
},
|
||
{
|
||
name: "happy path, device enrolled by Fleetie@example.com via Autopilot",
|
||
rows: []map[string]string{
|
||
{"name": "mdm-windows-hostname", "data": "mdm-windows-device-id"},
|
||
},
|
||
mdmEnrollUserID: "fleetie@example.com",
|
||
expectUpdateMDMWindowsEnrollmentsHostUUIDFuncInvoked: true,
|
||
expectReplaceHostDeviceMappingFuncInvoked: true,
|
||
expectScimUserByUserNameOrEmailFuncInvoked: true,
|
||
},
|
||
{
|
||
name: "device was enrolled by fleetie@example.com via Settings app",
|
||
rows: []map[string]string{
|
||
{"name": "mdm-windows-hostname", "data": "mdm-windows-device-id"},
|
||
},
|
||
mdmEnrollUserID: "fleetie@example.com",
|
||
mdmEnrollNotInOOBE: true,
|
||
expectUpdateMDMWindowsEnrollmentsHostUUIDFuncInvoked: true,
|
||
expectUpdateMDMInstalledFromDEPFuncInvoked: true,
|
||
expectScimUserByUserNameOrEmailFuncInvoked: true,
|
||
expectReplaceHostDeviceMappingFuncInvoked: true,
|
||
},
|
||
}
|
||
|
||
resetInvocationFlags := func() {
|
||
ds.UpdateMDMWindowsEnrollmentsHostUUIDFuncInvoked = false
|
||
ds.UpdateMDMInstalledFromDEPFuncInvoked = false
|
||
ds.ReplaceHostDeviceMappingFuncInvoked = false
|
||
ds.ScimUserByUserNameOrEmailFuncInvoked = false
|
||
ds.DeleteHostSCIMUserMappingFuncInvoked = false
|
||
}
|
||
|
||
for _, tc := range testCases {
|
||
t.Run(tc.name, func(t *testing.T) {
|
||
resetInvocationFlags()
|
||
|
||
// set base enrolled device state
|
||
if tc.mdmEnrollUserID != "" {
|
||
baseEnrolledDeviceToReturn.MDMEnrollUserID = tc.mdmEnrollUserID
|
||
} else {
|
||
// random string that looks like the sort of device IDs we get from the orbit enroll path
|
||
baseEnrolledDeviceToReturn.MDMEnrollUserID = "a1b2c3d4e5f6g7h8i9j0"
|
||
}
|
||
baseEnrolledDeviceToReturn.MDMNotInOOBE = tc.mdmEnrollNotInOOBE
|
||
|
||
// If no updates were done no further actions should be taken. This generic case covers this behavior.
|
||
if tc.expectUpdateMDMWindowsEnrollmentsHostUUIDFuncInvoked {
|
||
returnEnrollmentsUpdated = false
|
||
err := directIngestMDMDeviceIDWindows(ctx, logger, host, ds, tc.rows)
|
||
require.NoError(t, err)
|
||
|
||
require.Equal(t, tc.expectUpdateMDMWindowsEnrollmentsHostUUIDFuncInvoked, ds.UpdateMDMWindowsEnrollmentsHostUUIDFuncInvoked)
|
||
require.Equal(t, false, ds.UpdateMDMInstalledFromDEPFuncInvoked)
|
||
require.Equal(t, false, ds.ReplaceHostDeviceMappingFuncInvoked)
|
||
require.Equal(t, false, ds.ScimUserByUserNameOrEmailFuncInvoked)
|
||
require.Equal(t, false, ds.DeleteHostSCIMUserMappingFuncInvoked)
|
||
}
|
||
|
||
// Run the actual defined testcase
|
||
returnEnrollmentsUpdated = true
|
||
returnSCIMUser = true
|
||
err := directIngestMDMDeviceIDWindows(ctx, logger, host, ds, tc.rows)
|
||
if tc.expectError != "" {
|
||
require.ErrorContains(t, err, tc.expectError)
|
||
} else {
|
||
require.NoError(t, err)
|
||
}
|
||
|
||
require.Equal(t, tc.expectUpdateMDMWindowsEnrollmentsHostUUIDFuncInvoked, ds.UpdateMDMWindowsEnrollmentsHostUUIDFuncInvoked)
|
||
require.Equal(t, tc.expectUpdateMDMInstalledFromDEPFuncInvoked, ds.UpdateMDMInstalledFromDEPFuncInvoked)
|
||
require.Equal(t, tc.expectReplaceHostDeviceMappingFuncInvoked, ds.ReplaceHostDeviceMappingFuncInvoked)
|
||
require.Equal(t, tc.expectScimUserByUserNameOrEmailFuncInvoked, ds.ScimUserByUserNameOrEmailFuncInvoked)
|
||
// this test will always return a SCIM user if invoked and as such should update the mapping and never delete it
|
||
require.Equal(t, false, ds.DeleteHostSCIMUserMappingFuncInvoked)
|
||
|
||
// Test the case where no user is returned if applicable
|
||
if tc.expectScimUserByUserNameOrEmailFuncInvoked {
|
||
resetInvocationFlags()
|
||
|
||
returnSCIMUser = false
|
||
err = directIngestMDMDeviceIDWindows(ctx, logger, host, ds, tc.rows)
|
||
require.NoError(t, err)
|
||
|
||
require.Equal(t, tc.expectUpdateMDMWindowsEnrollmentsHostUUIDFuncInvoked, ds.UpdateMDMWindowsEnrollmentsHostUUIDFuncInvoked)
|
||
require.Equal(t, tc.expectUpdateMDMInstalledFromDEPFuncInvoked, ds.UpdateMDMInstalledFromDEPFuncInvoked)
|
||
require.Equal(t, tc.expectReplaceHostDeviceMappingFuncInvoked, ds.ReplaceHostDeviceMappingFuncInvoked)
|
||
require.Equal(t, tc.expectScimUserByUserNameOrEmailFuncInvoked, ds.ScimUserByUserNameOrEmailFuncInvoked)
|
||
// this test will never return a SCIM user if invoked and as such should always delete the mapping
|
||
require.Equal(t, true, ds.DeleteHostSCIMUserMappingFuncInvoked)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestShouldRemoveSoftware(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
want bool
|
||
s *fleet.Software
|
||
h *fleet.Host
|
||
}{
|
||
{
|
||
name: "parallels windows software on MacOS host",
|
||
want: true,
|
||
h: &fleet.Host{Platform: "darwin"},
|
||
s: &fleet.Software{BundleIdentifier: "com.parallels.winapp.notepad", Name: "Notepad.app"},
|
||
},
|
||
{
|
||
name: "regular macos software",
|
||
want: false,
|
||
h: &fleet.Host{Platform: "darwin"},
|
||
s: &fleet.Software{BundleIdentifier: "com.apple.dock", Name: "Dock.app"},
|
||
},
|
||
}
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
require.Equal(t, tt.want, shouldRemoveSoftware(tt.h, tt.s))
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestIngestNetworkInterface(t *testing.T) {
|
||
t.Parallel()
|
||
|
||
// NOTE: It was decided that we should allow ingesting private IPs on the PublicIP field,
|
||
// see https://github.com/fleetdm/fleet/issues/11102.
|
||
for _, tc := range []struct {
|
||
name string
|
||
ip string
|
||
valid bool
|
||
}{
|
||
{"public IPv6", "598b:6910:e935:63ff:54db:1753:9c01:4c84", true},
|
||
{"private IPv6", "fd42:fdaa:1234:5678::1a2b", true},
|
||
{"public IPv4", "190.18.97.12", true},
|
||
{"private IPv4", "127.0.0.1", true},
|
||
{"IP could not be determined", "", true},
|
||
{"invalid value ends up in the context", "invalid-ip", false},
|
||
} {
|
||
tc := tc
|
||
t.Run(tc.name, func(t *testing.T) {
|
||
h := fleet.Host{PublicIP: "190.18.97.3"} // set to some old value that should always be overridden
|
||
err := ingestNetworkInterface(publicip.NewContext(t.Context(), tc.ip), slog.New(slog.DiscardHandler), &h, nil)
|
||
require.NoError(t, err)
|
||
if tc.valid {
|
||
require.Equal(t, tc.ip, h.PublicIP)
|
||
} else {
|
||
require.Empty(t, h.PublicIP)
|
||
}
|
||
})
|
||
}
|
||
|
||
t.Run("primaryIP and primaryMAC", func(t *testing.T) {
|
||
h := fleet.Host{PublicIP: "190.18.97.3"} // set to some old value that should always be overridden
|
||
ip := "10.0.0.1"
|
||
var b bytes.Buffer
|
||
logger := slog.New(slog.NewTextHandler(&b, &slog.HandlerOptions{Level: slog.LevelDebug}))
|
||
|
||
// Happy path
|
||
rows := []map[string]string{
|
||
{"address": "address", "mac": "mac"},
|
||
}
|
||
err := ingestNetworkInterface(publicip.NewContext(t.Context(), ip), logger, &h, rows)
|
||
require.NoError(t, err)
|
||
assert.Equal(t, ip, h.PublicIP)
|
||
assert.Equal(t, "mac", h.PrimaryMac)
|
||
assert.Equal(t, "address", h.PrimaryIP)
|
||
assert.Empty(t, b.String())
|
||
|
||
// No rows
|
||
b.Reset()
|
||
h = fleet.Host{PublicIP: "190.18.97.3"}
|
||
err = ingestNetworkInterface(publicip.NewContext(t.Context(), ip), logger, &h, []map[string]string{})
|
||
require.NoError(t, err)
|
||
assert.Equal(t, ip, h.PublicIP)
|
||
assert.Empty(t, h.PrimaryMac)
|
||
assert.Empty(t, h.PrimaryIP)
|
||
assert.Contains(t, b.String(), "did not find a private IP address")
|
||
|
||
// Too many rows
|
||
b.Reset()
|
||
h = fleet.Host{PublicIP: "190.18.97.3"}
|
||
rows = []map[string]string{
|
||
{"address": "address", "mac": "mac"},
|
||
{"address": "address2", "mac": "mac2"},
|
||
}
|
||
err = ingestNetworkInterface(publicip.NewContext(t.Context(), ip), logger, &h, rows)
|
||
require.NoError(t, err)
|
||
assert.Equal(t, ip, h.PublicIP)
|
||
assert.Empty(t, h.PrimaryMac)
|
||
assert.Empty(t, h.PrimaryIP)
|
||
assert.Contains(t, b.String(), "expected single result")
|
||
})
|
||
}
|
||
|
||
func TestDirectIngestHostCertificates(t *testing.T) {
|
||
ds := new(mock.Store)
|
||
ctx := t.Context()
|
||
logger := slog.New(slog.DiscardHandler)
|
||
host := &fleet.Host{ID: 1, UUID: "host-uuid", Platform: "darwin"}
|
||
|
||
row1 := map[string]string{
|
||
"ca": "0",
|
||
"common_name": "Cert 1 Common Name",
|
||
"issuer": "/C=US/O=Issuer 1 Inc./CN=Issuer 1 Common Name",
|
||
"subject": "/C=US/O=Subject 1 Inc./OU=Subject 1 Org Unit/CN=Subject 1 Common Name",
|
||
"key_algorithm": "rsaEncryption",
|
||
"key_strength": "2048",
|
||
"key_usage": "Data Encipherment, Key Encipherment, Digital Signature",
|
||
"serial": "123abc",
|
||
"signing_algorithm": "sha256WithRSAEncryption",
|
||
"not_valid_after": "1822755797",
|
||
"not_valid_before": "1770228826",
|
||
"sha1": "9c1e9c00d8120c1a9d96274d2a17c38ffa30fd31",
|
||
"source": "user",
|
||
"path": "/Users/mna/Library/Keychains/login.keychain-db",
|
||
}
|
||
|
||
// row2 will be ingested correctly with the issue field containing a / in the value
|
||
row2 := map[string]string{
|
||
"ca": "1",
|
||
"common_name": "Cert 2 Common Name",
|
||
"issuer": `/C=US/O=Issuer 2 Inc.\/foobar/CN=Issuer 2 Common Name`,
|
||
"subject": "/C=US/O=Subject 1 Inc./OU=Subject 1 Org Unit/CN=Subject 1 Common Name",
|
||
"key_algorithm": "rsaEncryption",
|
||
"key_strength": "2048",
|
||
"key_usage": "Data Encipherment, Key Encipherment, Digital Signature",
|
||
"serial": "123abcd",
|
||
"signing_algorithm": "sha256WithRSAEncryption",
|
||
"not_valid_after": "1822755797",
|
||
"not_valid_before": "1770228826",
|
||
"sha1": "9c1e9c00d8120c1a9d96274d2a17c38ffa30fd32",
|
||
"source": "system",
|
||
"path": "/Library/Keychains/System.keychain",
|
||
}
|
||
|
||
ds.UpdateHostCertificatesFunc = func(ctx context.Context, hostID uint, hostUUID string, certs []*fleet.HostCertificateRecord) error {
|
||
require.Equal(t, host.ID, hostID)
|
||
require.Equal(t, host.UUID, hostUUID)
|
||
require.Len(t, certs, 2)
|
||
require.Equal(t, "9c1e9c00d8120c1a9d96274d2a17c38ffa30fd31", hex.EncodeToString(certs[0].SHA1Sum))
|
||
require.Equal(t, "Cert 1 Common Name", certs[0].CommonName)
|
||
require.Equal(t, "Subject 1 Common Name", certs[0].SubjectCommonName)
|
||
require.Equal(t, "Subject 1 Inc.", certs[0].SubjectOrganization)
|
||
require.Equal(t, "Subject 1 Org Unit", certs[0].SubjectOrganizationalUnit)
|
||
require.Equal(t, "US", certs[0].SubjectCountry)
|
||
require.Equal(t, "Issuer 1 Common Name", certs[0].IssuerCommonName)
|
||
require.Equal(t, "Issuer 1 Inc.", certs[0].IssuerOrganization)
|
||
require.Empty(t, certs[0].IssuerOrganizationalUnit)
|
||
require.Equal(t, "US", certs[0].IssuerCountry)
|
||
require.Equal(t, "rsaEncryption", certs[0].KeyAlgorithm)
|
||
require.Equal(t, 2048, certs[0].KeyStrength)
|
||
require.Equal(t, "Data Encipherment, Key Encipherment, Digital Signature", certs[0].KeyUsage)
|
||
require.Equal(t, "123abc", certs[0].Serial)
|
||
require.Equal(t, "sha256WithRSAEncryption", certs[0].SigningAlgorithm)
|
||
require.Equal(t, int64(1822755797), certs[0].NotValidAfter.Unix())
|
||
require.Equal(t, int64(1770228826), certs[0].NotValidBefore.Unix())
|
||
require.False(t, certs[0].CertificateAuthority)
|
||
require.EqualValues(t, "user", certs[0].Source)
|
||
require.Equal(t, "mna", certs[0].Username)
|
||
|
||
require.Equal(t, "9c1e9c00d8120c1a9d96274d2a17c38ffa30fd32", hex.EncodeToString(certs[1].SHA1Sum))
|
||
require.Equal(t, "Cert 2 Common Name", certs[1].CommonName)
|
||
require.Equal(t, "Subject 1 Common Name", certs[1].SubjectCommonName)
|
||
require.Equal(t, "Subject 1 Inc.", certs[1].SubjectOrganization)
|
||
require.Equal(t, "Subject 1 Org Unit", certs[1].SubjectOrganizationalUnit)
|
||
require.Equal(t, "US", certs[1].SubjectCountry)
|
||
require.Equal(t, "Issuer 2 Common Name", certs[1].IssuerCommonName)
|
||
require.Equal(t, "Issuer 2 Inc./foobar", certs[1].IssuerOrganization)
|
||
require.Empty(t, certs[1].IssuerOrganizationalUnit)
|
||
require.Equal(t, "US", certs[1].IssuerCountry)
|
||
require.Equal(t, "rsaEncryption", certs[1].KeyAlgorithm)
|
||
require.Equal(t, 2048, certs[1].KeyStrength)
|
||
require.Equal(t, "Data Encipherment, Key Encipherment, Digital Signature", certs[1].KeyUsage)
|
||
require.Equal(t, "123abcd", certs[1].Serial)
|
||
require.Equal(t, "sha256WithRSAEncryption", certs[1].SigningAlgorithm)
|
||
require.Equal(t, int64(1822755797), certs[1].NotValidAfter.Unix())
|
||
require.Equal(t, int64(1770228826), certs[1].NotValidBefore.Unix())
|
||
require.True(t, certs[1].CertificateAuthority)
|
||
require.EqualValues(t, "system", certs[1].Source)
|
||
|
||
return nil
|
||
}
|
||
|
||
err := directIngestHostCertificatesDarwin(ctx, logger, host, ds, []map[string]string{row1, row2})
|
||
require.NoError(t, err)
|
||
require.True(t, ds.UpdateHostCertificatesFuncInvoked)
|
||
}
|
||
|
||
func TestDirectIngestHostCertificatesDarwinHexEscapes(t *testing.T) {
|
||
ds := new(mock.Store)
|
||
ctx := t.Context()
|
||
logger := slog.New(slog.DiscardHandler)
|
||
host := &fleet.Host{ID: 1, UUID: "host-uuid", Platform: "darwin"}
|
||
|
||
// Simulate osquery outputting Cyrillic characters as literal \xHH escape
|
||
// sequences. "АБ" in UTF-8 is bytes D0 90 D0 91, which osquery returns as
|
||
// the 16-character ASCII string `\xD0\x90\xD0\x91`.
|
||
row := map[string]string{
|
||
"ca": "0",
|
||
"common_name": `\xD0\x90\xD0\x91`,
|
||
"subject": `/C=US/O=\xD0\x90\xD0\x91/OU=\xD0\x92\xD0\x93/CN=\xD0\x94\xD0\x95`,
|
||
"issuer": `/O=\xD0\x96\xD0\x97/CN=\xD0\x98\xD0\x9A`,
|
||
"key_algorithm": "rsaEncryption",
|
||
"key_strength": "2048",
|
||
"key_usage": "Digital Signature",
|
||
"serial": "abc123",
|
||
"signing_algorithm": "sha256WithRSAEncryption",
|
||
"not_valid_after": "1822755797",
|
||
"not_valid_before": "1770228826",
|
||
"sha1": "aabbccdd00112233445566778899aabbccddeeff",
|
||
"source": "system",
|
||
"path": "/Library/Keychains/System.keychain",
|
||
}
|
||
|
||
ds.UpdateHostCertificatesFunc = func(ctx context.Context, hostID uint, hostUUID string, certs []*fleet.HostCertificateRecord) error {
|
||
require.Len(t, certs, 1)
|
||
cert := certs[0]
|
||
|
||
assert.Equal(t, "АБ", cert.CommonName)
|
||
assert.Equal(t, "ДЕ", cert.SubjectCommonName)
|
||
assert.Equal(t, "АБ", cert.SubjectOrganization)
|
||
assert.Equal(t, "ВГ", cert.SubjectOrganizationalUnit)
|
||
assert.Equal(t, "US", cert.SubjectCountry)
|
||
assert.Equal(t, "ИК", cert.IssuerCommonName)
|
||
assert.Equal(t, "ЖЗ", cert.IssuerOrganization)
|
||
|
||
return nil
|
||
}
|
||
|
||
err := directIngestHostCertificatesDarwin(ctx, logger, host, ds, []map[string]string{row})
|
||
require.NoError(t, err)
|
||
require.True(t, ds.UpdateHostCertificatesFuncInvoked)
|
||
}
|
||
|
||
func TestDirectIngestHostCertificatesWindows(t *testing.T) {
|
||
ds := new(mock.Store)
|
||
ctx := t.Context()
|
||
logger := slog.New(slog.DiscardHandler)
|
||
host := &fleet.Host{ID: 1, UUID: "host-uuid", Platform: "windows"}
|
||
|
||
// Fleet SCEP cert example based on data from a real Windows host
|
||
c1 := map[string]string{
|
||
"ca": "-1",
|
||
"common_name": "494FE0F794940E21C757B790494B0FAFD97CFA4D5E9CC75856DB00DE78F3958D",
|
||
"subject": "Fleet, 494FE0F794940E21C757B790494B0FAFD97CFA4D5E9CC75856DB00DE78F3958D",
|
||
"issuer": "\"\", scep-ca, SCEP CA, FleetDM",
|
||
"key_algorithm": "RSA",
|
||
"key_strength": "2160",
|
||
"key_usage": "CERT_KEY_ENCIPHERMENT_KEY_USAGE,CERT_DIGITAL_SIGNATURE_KEY_USAGE",
|
||
"signing_algorithm": "sha256RSA",
|
||
"not_valid_after": "1780784467",
|
||
"not_valid_before": "1749248467",
|
||
"serial": "05",
|
||
"sha1": "1A395245953C61AE12657704FF45F31A1E7BC1E8",
|
||
"username": "Admin",
|
||
"path": "Users\\S-1-5-21-1043593016-4249271388-1765263865-1000\\Personal",
|
||
}
|
||
// Custom SCEP cert example based on data from a real Windows host
|
||
c2 := map[string]string{
|
||
"ca": "-1",
|
||
"common_name": "wc215384b-5a6e-4ca5-a2a3-1289734a5a71 User\n CN",
|
||
"subject": "fleet-w2a6fd2c4-0018-4bdc-8046-c7342962b576, \"wc215384b-5a6e-4ca5-a2a3-1289734a5a71 User\n CN\"",
|
||
"issuer": "US, scep-ca, SCEP CA, MICROMDM SCEP CA",
|
||
"key_algorithm": "RSA",
|
||
"key_strength": "1120",
|
||
"key_usage": "CERT_DIGITAL_SIGNATURE_KEY_USAGE",
|
||
"signing_algorithm": "sha256RSA",
|
||
"not_valid_after": "1796430423",
|
||
"not_valid_before": "1764893823",
|
||
"serial": "23",
|
||
"sha1": "EE5E756CC1A0782078C7C45180A4544A37D0F6D7",
|
||
"username": "Admin",
|
||
"path": "Users\\S-1-5-21-1043593016-4249271388-1765263865-1000\\Personal",
|
||
}
|
||
|
||
// We'll use the examples above to create rows with minor variations, similar to what
|
||
// we would get from a real Windows host.
|
||
c3 := maps.Clone(c1)
|
||
c3["username"] = "SYSTEM"
|
||
c3["path"] = "Users\\S-1-5-18\\Personal"
|
||
|
||
c4 := maps.Clone(c1)
|
||
c4["username"] = "SYSTEM"
|
||
c4["path"] = "CurrentUser\\Personal"
|
||
|
||
c5 := maps.Clone(c1)
|
||
c5["username"] = "SYSTEM"
|
||
c5["path"] = "Users\\S-1-5-18\\Personal"
|
||
|
||
c6 := maps.Clone(c1)
|
||
c6["path"] = "Users\\S-1-5-21-1043593016-4249271388-1765263865-1000_Classes\\Personal"
|
||
|
||
c7 := maps.Clone(c2)
|
||
c7["path"] = "Users\\S-1-5-21-1043593016-4249271388-1765263865-1000_Classes\\Personal"
|
||
|
||
rows := []map[string]string{c1, c2, c3, c4, c5, c6, c7}
|
||
|
||
ds.UpdateHostCertificatesFunc = func(ctx context.Context, hostID uint, hostUUID string, certs []*fleet.HostCertificateRecord) error {
|
||
require.Equal(t, host.ID, hostID)
|
||
require.Equal(t, host.UUID, hostUUID)
|
||
require.Len(t, certs, 3)
|
||
|
||
// We expect that the ingest function will deduplicate certs based on SHA1+username
|
||
// so we should see only 3 unique combinations from the 7 rows above.
|
||
expectSha1Users := map[string]bool{
|
||
"1A395245953C61AE12657704FF45F31A1E7BC1E8" + "Admin": true, // c1, c6
|
||
"1A395245953C61AE12657704FF45F31A1E7BC1E8" + "SYSTEM": true, // c3, c4, c5
|
||
"EE5E756CC1A0782078C7C45180A4544A37D0F6D7" + "Admin": true, // c2, c7
|
||
}
|
||
seenSha1Users := map[string]bool{}
|
||
for _, cert := range certs {
|
||
s := strings.ToUpper(hex.EncodeToString(cert.SHA1Sum))
|
||
_, ok := expectSha1Users[s+cert.Username]
|
||
require.True(t, ok, "unexpected cert SHA1+username combination: %s + %s", s, cert.Username)
|
||
seenSha1Users[s+cert.Username] = true
|
||
|
||
// Validate fields that differ between the cert examples
|
||
switch s {
|
||
case "1A395245953C61AE12657704FF45F31A1E7BC1E8":
|
||
require.Equal(t, "CERT_KEY_ENCIPHERMENT_KEY_USAGE,CERT_DIGITAL_SIGNATURE_KEY_USAGE", cert.KeyUsage)
|
||
require.Equal(t, "05", cert.Serial)
|
||
require.Equal(t, int64(1780784467), cert.NotValidAfter.Unix())
|
||
require.Equal(t, int64(1749248467), cert.NotValidBefore.Unix())
|
||
require.Equal(t, 2160, cert.KeyStrength)
|
||
require.Equal(t, "494FE0F794940E21C757B790494B0FAFD97CFA4D5E9CC75856DB00DE78F3958D", cert.CommonName)
|
||
require.Equal(t, "Fleet, 494FE0F794940E21C757B790494B0FAFD97CFA4D5E9CC75856DB00DE78F3958D", cert.SubjectCommonName)
|
||
require.Equal(t, "\"\", scep-ca, SCEP CA, FleetDM", cert.IssuerCommonName)
|
||
require.Contains(t, []string{"Admin", "SYSTEM"}, cert.Username)
|
||
|
||
case "EE5E756CC1A0782078C7C45180A4544A37D0F6D7":
|
||
require.Equal(t, "CERT_DIGITAL_SIGNATURE_KEY_USAGE", cert.KeyUsage)
|
||
require.Equal(t, "23", cert.Serial)
|
||
require.Equal(t, int64(1796430423), cert.NotValidAfter.Unix())
|
||
require.Equal(t, int64(1764893823), cert.NotValidBefore.Unix())
|
||
require.Equal(t, 1120, cert.KeyStrength)
|
||
require.Equal(t, "wc215384b-5a6e-4ca5-a2a3-1289734a5a71 User\n CN", cert.CommonName)
|
||
require.Equal(t, "fleet-w2a6fd2c4-0018-4bdc-8046-c7342962b576, \"wc215384b-5a6e-4ca5-a2a3-1289734a5a71 User\n CN\"", cert.SubjectCommonName)
|
||
require.Equal(t, "US, scep-ca, SCEP CA, MICROMDM SCEP CA", cert.IssuerCommonName)
|
||
require.Equal(t, "Admin", cert.Username)
|
||
|
||
default:
|
||
t.Fatalf("unexpected cert SHA1: %s", s)
|
||
}
|
||
|
||
// Validate fields common across all Windows certs in this test
|
||
require.Equal(t, "RSA", cert.KeyAlgorithm)
|
||
require.Equal(t, "sha256RSA", cert.SigningAlgorithm)
|
||
require.False(t, cert.CertificateAuthority)
|
||
if cert.Username == "SYSTEM" {
|
||
require.Equal(t, fleet.SystemHostCertificate, cert.Source)
|
||
} else {
|
||
require.Equal(t, fleet.UserHostCertificate, cert.Source)
|
||
}
|
||
|
||
// For Windows certs, osquery squeezes all distinguished name fields into
|
||
// the comma-separated list that we store as Issuer/SubjectCommonName and
|
||
// we leave all other fields empty for now (see fleet.ExtractDetailsFromOsqueryDistinguishedName)
|
||
require.Empty(t, cert.SubjectOrganization)
|
||
require.Empty(t, cert.SubjectOrganizationalUnit)
|
||
require.Empty(t, cert.SubjectCountry)
|
||
require.Empty(t, cert.IssuerOrganization)
|
||
require.Empty(t, cert.IssuerOrganizationalUnit)
|
||
require.Empty(t, cert.IssuerCountry)
|
||
|
||
}
|
||
require.Equal(t, expectSha1Users, seenSha1Users)
|
||
|
||
return nil
|
||
}
|
||
|
||
err := directIngestHostCertificatesWindows(ctx, logger, host, ds, rows)
|
||
require.NoError(t, err)
|
||
require.True(t, ds.UpdateHostCertificatesFuncInvoked)
|
||
}
|
||
|
||
func TestGenerateSQLForAllExists(t *testing.T) {
|
||
// Combine two queries
|
||
query1 := "SELECT 1 WHERE foo = bar"
|
||
query2 := "SELECT 1 WHERE baz = qux"
|
||
sql := generateSQLForAllExists(query1, query2)
|
||
assert.Equal(t, "SELECT 1 WHERE EXISTS (SELECT 1 WHERE foo = bar) AND EXISTS (SELECT 1 WHERE baz = qux)", sql)
|
||
|
||
// Default
|
||
sql = generateSQLForAllExists()
|
||
require.Equal(t, "SELECT 0 LIMIT 0", sql)
|
||
|
||
// sanitize semicolons from subqueries
|
||
query1 = "SELECT 1 WHERE foo = bar;"
|
||
query2 = "SELECT 1 WHERE baz = qux;"
|
||
sql = generateSQLForAllExists(query1, query2)
|
||
assert.Equal(t, "SELECT 1 WHERE EXISTS (SELECT 1 WHERE foo = bar) AND EXISTS (SELECT 1 WHERE baz = qux)", sql)
|
||
|
||
// sanitize only trailing semicolons
|
||
query1 = "SELECT 1 WHERE foo = 'ba;r';"
|
||
query2 = "SELECT 1 WHERE baz = 'qu;x';;; "
|
||
sql = generateSQLForAllExists(query1, query2)
|
||
assert.Equal(t, "SELECT 1 WHERE EXISTS (SELECT 1 WHERE foo = 'ba;r') AND EXISTS (SELECT 1 WHERE baz = 'qu;x')", sql)
|
||
}
|
||
|
||
func TestLuksVerifyQueryDiscovery(t *testing.T) {
|
||
lsblkTbl := "SELECT 1 FROM osquery_registry WHERE active = true AND registry = 'table' AND name = 'lsblk'"
|
||
cryptsetupLuksSaltTbl := "SELECT 1 FROM osquery_registry WHERE active = true AND registry = 'table' AND name = 'cryptsetup_luks_salt'"
|
||
|
||
require.Equal(t,
|
||
fmt.Sprintf("SELECT 1 WHERE EXISTS (%s) AND EXISTS (%s);", lsblkTbl, cryptsetupLuksSaltTbl),
|
||
luksVerifyQuery.Discovery,
|
||
)
|
||
}
|
||
|
||
func TestLuksVerifyQueryIngester(t *testing.T) {
|
||
decrypter := func(encrypted string) (string, error) {
|
||
return encrypted, nil
|
||
}
|
||
ctx := t.Context()
|
||
logger := slog.New(slog.DiscardHandler)
|
||
|
||
nonLUKSHost := &fleet.Host{ID: 1, Platform: "skynet"}
|
||
luksHost := &fleet.Host{ID: 1, Platform: "ubuntu"}
|
||
|
||
testCases := []struct {
|
||
name string
|
||
rows []map[string]string
|
||
err error
|
||
host *fleet.Host
|
||
setUp func(t *testing.T, ds *mock.Store)
|
||
expectations func(t *testing.T, ds *mock.Store, err error)
|
||
}{
|
||
{
|
||
name: "No results",
|
||
expectations: func(t *testing.T, ds *mock.Store, err error) {
|
||
require.NoError(t, err)
|
||
require.False(t, ds.GetHostDiskEncryptionKeyFuncInvoked)
|
||
require.False(t, ds.DeleteLUKSDataFuncInvoked)
|
||
},
|
||
},
|
||
{
|
||
name: "host is not LUKS capable",
|
||
host: nonLUKSHost,
|
||
rows: []map[string]string{
|
||
{
|
||
"key_slot": "0",
|
||
"salt": "some salty bits",
|
||
},
|
||
},
|
||
expectations: func(t *testing.T, ds *mock.Store, err error) {
|
||
require.NoError(t, err)
|
||
require.False(t, ds.GetHostDiskEncryptionKeyFuncInvoked)
|
||
require.False(t, ds.DeleteLUKSDataFuncInvoked)
|
||
},
|
||
},
|
||
{
|
||
name: "disk encryption entry not found on DB",
|
||
host: luksHost,
|
||
rows: []map[string]string{
|
||
{
|
||
"key_slot": "0",
|
||
"salt": "some salty bits",
|
||
},
|
||
},
|
||
setUp: func(t *testing.T, ds *mock.Store) {
|
||
ds.GetHostDiskEncryptionKeyFunc = func(ctx context.Context, hostID uint) (*fleet.HostDiskEncryptionKey, error) {
|
||
require.Equal(t, uint(1), hostID)
|
||
return nil, common_mysql.NotFound("HostDiskEncryptionKey")
|
||
}
|
||
},
|
||
expectations: func(t *testing.T, ds *mock.Store, err error) {
|
||
require.NoError(t, err)
|
||
require.False(t, ds.DeleteLUKSDataFuncInvoked)
|
||
},
|
||
},
|
||
{
|
||
name: "error is thrown while getting the host disk encryption key",
|
||
host: luksHost,
|
||
rows: []map[string]string{
|
||
{
|
||
"key_slot": "0",
|
||
"salt": "some salty bits",
|
||
},
|
||
},
|
||
setUp: func(t *testing.T, ds *mock.Store) {
|
||
ds.GetHostDiskEncryptionKeyFunc = func(ctx context.Context, hostID uint) (*fleet.HostDiskEncryptionKey, error) {
|
||
require.Equal(t, uint(1), hostID)
|
||
return nil, errors.New("some error")
|
||
}
|
||
},
|
||
expectations: func(t *testing.T, ds *mock.Store, err error) {
|
||
require.Error(t, err)
|
||
require.False(t, ds.DeleteLUKSDataFuncInvoked)
|
||
},
|
||
},
|
||
{
|
||
name: "stored key matches the one reported",
|
||
host: luksHost,
|
||
rows: []map[string]string{
|
||
{
|
||
"key_slot": "0",
|
||
"salt": "some salty bits",
|
||
},
|
||
},
|
||
setUp: func(t *testing.T, ds *mock.Store) {
|
||
ds.GetHostDiskEncryptionKeyFunc = func(ctx context.Context, hostID uint) (*fleet.HostDiskEncryptionKey, error) {
|
||
require.Equal(t, uint(1), hostID)
|
||
return &fleet.HostDiskEncryptionKey{
|
||
KeySlot: ptr.Uint(0),
|
||
Base64EncryptedSalt: "some salty bits",
|
||
}, nil
|
||
}
|
||
},
|
||
expectations: func(t *testing.T, ds *mock.Store, err error) {
|
||
require.NoError(t, err)
|
||
require.False(t, ds.DeleteLUKSDataFuncInvoked)
|
||
},
|
||
},
|
||
{
|
||
name: "stored key does not match the one reported",
|
||
host: luksHost,
|
||
rows: []map[string]string{
|
||
{
|
||
"key_slot": "0",
|
||
"salt": "some sour bits",
|
||
},
|
||
{
|
||
"key_slot": "1",
|
||
"salt": "some spicy bits",
|
||
},
|
||
},
|
||
setUp: func(t *testing.T, ds *mock.Store) {
|
||
ds.GetHostDiskEncryptionKeyFunc = func(ctx context.Context, hostID uint) (*fleet.HostDiskEncryptionKey, error) {
|
||
require.Equal(t, uint(1), hostID)
|
||
return &fleet.HostDiskEncryptionKey{
|
||
KeySlot: ptr.Uint(0),
|
||
Base64EncryptedSalt: base64.StdEncoding.EncodeToString([]byte("some salty bits")),
|
||
}, nil
|
||
}
|
||
ds.DeleteLUKSDataFunc = func(ctx context.Context, hostID uint, keySlot uint) error {
|
||
require.Equal(t, uint(1), hostID)
|
||
return nil
|
||
}
|
||
},
|
||
expectations: func(t *testing.T, ds *mock.Store, err error) {
|
||
require.NoError(t, err)
|
||
require.True(t, ds.DeleteLUKSDataFuncInvoked)
|
||
},
|
||
},
|
||
}
|
||
|
||
sut := luksVerifyQueryIngester(decrypter)
|
||
for _, tc := range testCases {
|
||
t.Run(tc.name, func(t *testing.T) {
|
||
ds := new(mock.Store)
|
||
if tc.setUp != nil {
|
||
tc.setUp(t, ds)
|
||
}
|
||
tc.expectations(t, ds, sut(ctx, logger, tc.host, ds, tc.rows))
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestUserIngestNoUID(t *testing.T) {
|
||
ctx := t.Context()
|
||
host := fleet.Host{ID: 1}
|
||
ds := new(mock.Store)
|
||
savedUsers := 0
|
||
|
||
ds.SaveHostUsersFunc = func(ctx context.Context, hostID uint, users []fleet.HostUser) error {
|
||
savedUsers = len(users)
|
||
return nil
|
||
}
|
||
|
||
input := []map[string]string{
|
||
{"uid": "1000", "shell": "/bin/sh"},
|
||
// Missing uid
|
||
{"shell": "/bin/sh"},
|
||
}
|
||
|
||
err := usersQuery.DirectIngestFunc(ctx, nil, &host, ds, input)
|
||
require.NoError(t, err)
|
||
// Saved the good user, ignored the one missing a uid
|
||
require.Equal(t, 1, savedUsers)
|
||
}
|
||
|
||
func TestUserIngestMacosUpdateManagedUser(t *testing.T) {
|
||
ctx := t.Context()
|
||
host := fleet.Host{ID: 1, UUID: "host-uuid", Platform: "darwin"}
|
||
ds := new(mock.Store)
|
||
userUUIDForUpdate := "uuid-1234"
|
||
|
||
ds.GetNanoMDMUserEnrollmentUsernameAndUUIDFunc = func(ctx context.Context, hostUUID string) (string, string, error) {
|
||
return "fleetie", userUUIDForUpdate, nil
|
||
}
|
||
|
||
ds.UpdateNanoMDMUserEnrollmentUsernameFunc = func(ctx context.Context, deviceID string, userUUID string, username string) error {
|
||
require.Equal(t, host.UUID, deviceID)
|
||
require.Equal(t, userUUIDForUpdate, userUUID)
|
||
require.Equal(t, "new fleetie", username)
|
||
return nil
|
||
}
|
||
|
||
ds.SaveHostUsersFunc = func(ctx context.Context, hostID uint, users []fleet.HostUser) error {
|
||
require.Len(t, users, 2)
|
||
return nil
|
||
}
|
||
|
||
input := []map[string]string{
|
||
{"uid": "1000", "shell": "/bin/sh", "username": "new fleetie", "uuid": userUUIDForUpdate},
|
||
{"uid": "500", "shell": "/bin/sh", "username": "someone else", "uuid": "some-other-uuid"},
|
||
}
|
||
|
||
err := usersQuery.DirectIngestFunc(ctx, nil, &host, ds, input)
|
||
require.NoError(t, err)
|
||
require.True(t, ds.UpdateNanoMDMUserEnrollmentUsernameFuncInvoked)
|
||
require.True(t, ds.SaveHostUsersFuncInvoked)
|
||
}
|
||
|
||
func TestMaxString(t *testing.T) {
|
||
t.Parallel()
|
||
testCases := []struct {
|
||
a string
|
||
b string
|
||
want string
|
||
}{
|
||
{a: "", b: "", want: ""},
|
||
{a: "1", b: "", want: "1"},
|
||
{a: "", b: "2", want: "2"},
|
||
{a: "1751737544", b: "1751737555", want: "1751737555"},
|
||
}
|
||
for _, tc := range testCases {
|
||
t.Run(fmt.Sprintf("a=%s,b=%s", tc.a, tc.b), func(t *testing.T) {
|
||
got := maxString(tc.a, tc.b)
|
||
require.Equal(t, tc.want, got)
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestWindowsLastOpenedAt(t *testing.T) {
|
||
processFunc := SoftwareOverrideQueries["windows_last_opened_at"].SoftwareProcessResults
|
||
prefetchResults := []map[string]string{
|
||
{"executable_path": "", "last_opened_at": "1751756656"},
|
||
{"executable_path": "\\PROGRAM FILES (X86)\\MICROSOFT\\EDGECORE\\135.0.3179.73\\MSEDGEWEBVIEW2.EXE", "last_opened_at": "1744841906"},
|
||
{"executable_path": "\\PROGRAM FILES (X86)\\MICROSOFT\\EDGEUPDATE\\MICROSOFTEDGEUPDATE.EXE", "last_opened_at": "1751755072"},
|
||
{"executable_path": "\\PROGRAM FILES (X86)\\MICROSOFT\\EDGEWEBVIEW\\APPLICATION\\135.0.3179.54\\MSEDGEWEBVIEW2.EXE", "last_opened_at": "1744305414"},
|
||
{"executable_path": "\\PROGRAM FILES (X86)\\MICROSOFT\\EDGEWEBVIEW\\APPLICATION\\136.0.3240.64\\MSEDGEWEBVIEW2.EXE", "last_opened_at": "1747354446"},
|
||
{"executable_path": "\\PROGRAM FILES (X86)\\MICROSOFT\\EDGEWEBVIEW\\APPLICATION\\137.0.3296.68\\MSEDGEWEBVIEW2.EXE", "last_opened_at": "1751739680"},
|
||
{"executable_path": "\\PROGRAM FILES (X86)\\MICROSOFT\\EDGEWEBVIEW\\APPLICATION\\137.0.3296.93\\MSEDGEWEBVIEW2.EXE", "last_opened_at": "1751755195"},
|
||
{"executable_path": "\\PROGRAM FILES (X86)\\MICROSOFT\\EDGE\\APPLICATION\\MSEDGE.EXE", "last_opened_at": "1744224613"},
|
||
{"executable_path": "\\PROGRAM FILES\\WINDOWSAPPS\\MICROSOFT.MSPAINT_6.2410.13017.0_X64__8WEKYB3D8BBWE\\PAINTSTUDIO.VIEW.EXE", "last_opened_at": "1751739848"},
|
||
{"executable_path": "\\PROGRAM FILES\\WINDOWSAPPS\\MICROSOFT.PAINT_11.2504.531.0_X64__8WEKYB3D8BBWE\\PAINTAPP\\MSPAINT.EXE", "last_opened_at": "1751739842"},
|
||
{"executable_path": "\\PROGRAM FILES\\CMAKE\\BIN\\CMAKE.EXE", "last_opened_at": "1751756660"},
|
||
{"executable_path": "\\PROGRAM FILES\\CMAKE\\BIN\\CTEST.EXE", "last_opened_at": "1751756665"},
|
||
{"executable_path": "\\PROGRAM FILES\\GIT\\CMD\\GIT.EXE", "last_opened_at": "1751756656"},
|
||
{"executable_path": "\\PROGRAM FILES\\MOZILLA FIREFOX\\CRASHHELPER.EXE", "last_opened_at": "1751650445"},
|
||
{"executable_path": "\\PROGRAM FILES\\MOZILLA FIREFOX\\FIREFOX.EXE", "last_opened_at": "1751755087"},
|
||
{"executable_path": "\\PROGRAM FILES\\MOZILLA FIREFOX\\PINGSENDER.EXE", "last_opened_at": "1750358363"},
|
||
{"executable_path": "\\PROGRAM FILES\\POWERTOYS\\POWERTOYS.EXE", "last_opened_at": "1747935582"},
|
||
{"executable_path": "\\STRAWBERRY\\PERL\\BIN\\PERL.EXE", "last_opened_at": "1749664524"},
|
||
{"executable_path": "\\USERS\\ZACH\\.VSCODE\\EXTENSIONS\\MS-VSCODE.CPPTOOLS-1.24.5-WIN32-X64\\BIN\\CPPTOOLS-SRV.EXE", "last_opened_at": "1749678210"},
|
||
{"executable_path": "\\USERS\\ZACH\\.VSCODE\\EXTENSIONS\\MS-VSCODE.CPPTOOLS-1.24.5-WIN32-X64\\LLVM\\BIN\\CLANG-FORMAT.EXE", "last_opened_at": "1749678028"},
|
||
{"executable_path": "\\USERS\\ZACH\\.VSCODE\\EXTENSIONS\\MS-VSCODE.CPPTOOLS-1.25.3-WIN32-X64\\BIN\\CPPTOOLS-SRV.EXE", "last_opened_at": "1751739766"},
|
||
{"executable_path": "\\USERS\\ZACH\\.VSCODE\\EXTENSIONS\\MS-VSCODE.CPPTOOLS-1.25.3-WIN32-X64\\BIN\\CPPTOOLS.EXE", "last_opened_at": "1751739736"},
|
||
{"executable_path": "\\USERS\\ZACH\\.VSCODE\\EXTENSIONS\\MS-VSCODE.CPPTOOLS-1.26.3-WIN32-X64\\BIN\\CPPTOOLS-SRV.EXE", "last_opened_at": "1751756684"},
|
||
{"executable_path": "\\USERS\\ZACH\\.VSCODE\\EXTENSIONS\\MS-VSCODE.CPPTOOLS-1.26.3-WIN32-X64\\BIN\\CPPTOOLS.EXE", "last_opened_at": "1751756656"},
|
||
{"executable_path": "\\USERS\\ZACH\\APPDATA\\LOCAL\\1PASSWORD\\APP\\8\\1PASSWORD-BROWSERSUPPORT.EXE", "last_opened_at": "1751650028"},
|
||
{"executable_path": "\\USERS\\ZACH\\APPDATA\\LOCAL\\1PASSWORD\\APP\\8\\1PASSWORD.EXE", "last_opened_at": "1751755191"},
|
||
{"executable_path": "\\USERS\\ZACH\\APPDATA\\LOCAL\\1PASSWORD\\UPDATE\\8\\1PASSWORD.EXE", "last_opened_at": "1751650139"},
|
||
{"executable_path": "\\USERS\\ZACH\\APPDATA\\LOCAL\\PROGRAMS\\MICROSOFT VS CODE\\BIN\\CODE-TUNNEL.EXE", "last_opened_at": "1751756657"},
|
||
{"executable_path": "\\USERS\\ZACH\\APPDATA\\LOCAL\\PROGRAMS\\MICROSOFT VS CODE\\CODE.EXE", "last_opened_at": "1751756772"},
|
||
{"executable_path": "\\USERS\\ZACH\\APPDATA\\LOCAL\\PROGRAMS\\MICROSOFT VS CODE\\RESOURCES\\APP\\NODE_MODULES\\@VSCODE\\RIPGREP\\BIN\\RG.EXE", "last_opened_at": "1751756656"},
|
||
{"executable_path": "\\USERS\\ZACH\\APPDATA\\LOCAL\\PROGRAMS\\MICROSOFT VS CODE\\RESOURCES\\APP\\NODE_MODULES\\@VSCODE\\VSCE-SIGN\\BIN\\VSCE-SIGN.EXE", "last_opened_at": "1751739739"},
|
||
{"executable_path": "\\USERS\\ZACH\\APPDATA\\LOCAL\\PROGRAMS\\MICROSOFT VS CODE\\TOOLS\\INNO_UPDATER.EXE", "last_opened_at": "1751739781"},
|
||
}
|
||
softwareResults := []map[string]string{
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "", "source": "ie_extensions", "vendor": "", "version": ""},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Windows\\System32\\", "name": "", "source": "programs", "vendor": "Microsoft Corporation", "version": ""},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\windows\\System32\\", "name": "", "source": "programs", "vendor": "Microsoft Corporation", "version": ""},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "", "source": "programs", "vendor": "", "version": "1.3.195.61"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Windows\\SystemApps\\Microsoft.Windows.FilePicker_cw5n1h2txyewy", "name": "1527c705-839a-4832-9118-54d4Bd6a0c89", "source": "programs", "vendor": "Microsoft Corporation", "version": "10.0.19640.1000"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Users\\zach\\AppData\\Local\\1Password\\app\\8", "name": "1Password", "source": "programs", "vendor": "AgileBits, Inc.", "version": "8.10.82"},
|
||
{"extension_for": "firefox", "extension_id": "{d634138d-c276-4fc8-924b-40a0ea21d284}", "installed_path": "C:\\Users\\zach\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\0oxsfufm.default-release\\extensions\\{d634138d-c276-4fc8-924b-40a0ea21d284}.xpi", "name": "1Password – Password Manager", "source": "firefox_addons", "vendor": "", "version": "8.10.76.34"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\AppUp.IntelGraphicsExperience_1.100.5688.0_x64__8j3eq9eme6ctt", "name": "AppUp.IntelGraphicsExperience", "source": "programs", "vendor": "INTEL CORP", "version": "1.100.5688.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "Application Verifier x64 External Package", "source": "programs", "vendor": "Microsoft", "version": "10.1.20348.1"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\CMake\\", "name": "CMake", "source": "programs", "vendor": "Kitware", "version": "3.28.1"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Windows\\SystemApps\\Microsoft.Windows.CapturePicker_cw5n1h2txyewy", "name": "CapturePicker", "source": "programs", "vendor": "Microsoft Corporation", "version": "10.0.19580.1000"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\STMicroelectronicsMEMS.DellFreeFallDataProtection_1.0.27.0_x64__rp6h1c31mfy1y", "name": "Dell Free Fall Data Protection", "source": "programs", "vendor": "STMICROELECTRONICS S.R.L.", "version": "1.0.27.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\WINDOWS\\system32\\DellTPad", "name": "Dell PointStick Driver", "source": "programs", "vendor": "ALPS ELECTRIC CO., LTD.", "version": "10.3201.101.326"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Windows\\SystemApps\\Microsoft.Windows.AppResolverUX_cw5n1h2txyewy", "name": "E2A4F912-2574-4A75-9BB0-0D023378592B", "source": "programs", "vendor": "Microsoft Corporation", "version": "10.0.19640.1000"},
|
||
{"extension_for": "edge", "extension_id": "jmjflgjpcpepeafmmgdpfkogkghcpiha", "installed_path": "C:\\Users\\zach\\AppData\\Local\\Microsoft\\Edge\\User Data\\Default\\Extensions\\jmjflgjpcpepeafmmgdpfkogkghcpiha\\1.2.1_0", "name": "Edge relevant text changes", "source": "chrome_extensions", "vendor": "", "version": "1.2.1"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Windows\\SystemApps\\Microsoft.Windows.AddSuggestedFoldersToLibraryDialog_cw5n1h2txyewy", "name": "F46D4000-FD22-4DB4-AC8E-4E1DDDE828FE", "source": "programs", "vendor": "Microsoft Corporation", "version": "10.0.26100.1"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "Fleet osquery", "source": "programs", "vendor": "Fleet Device Management (fleetdm.com)", "version": "1.44.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.XboxGamingOverlay_7.325.5191.0_x64__8wekyb3d8bbwe", "name": "Game Bar", "source": "programs", "vendor": "Microsoft Corporation", "version": "7.325.5191.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\Git\\", "name": "Git", "source": "programs", "vendor": "The Git Development Community", "version": "2.43.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "GitHub CLI", "source": "programs", "vendor": "GitHub, Inc.", "version": "2.69.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\138.0.3351.65\\BHO\\ie_to_edge_bho_64.dll", "name": "IEToEdge BHO", "source": "ie_extensions", "vendor": "", "version": "138.0.3351.65"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\AppUp.IntelOptaneMemoryandStorageManagement_18.1.1042.0_x64__8j3eq9eme6ctt", "name": "Intel® Optane™ Memory and Storage Management", "source": "programs", "vendor": "INTEL CORP", "version": "18.1.1042.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.NET.Native.Framework.1.7_1.7.27413.0_x64__8wekyb3d8bbwe", "name": "Microsoft .Net Native Framework Package 1.7", "source": "programs", "vendor": "Microsoft Corporation", "version": "1.7.27413.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.NET.Native.Framework.2.2_2.2.29512.0_x64__8wekyb3d8bbwe", "name": "Microsoft .Net Native Framework Package 2.2", "source": "programs", "vendor": "Microsoft Corporation", "version": "2.2.29512.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.NET.Native.Runtime.1.7_1.7.27422.0_x64__8wekyb3d8bbwe", "name": "Microsoft .Net Native Runtime Package 1.7", "source": "programs", "vendor": "Microsoft Corporation", "version": "1.7.27422.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.NET.Native.Runtime.2.2_2.2.28604.0_x64__8wekyb3d8bbwe", "name": "Microsoft .Net Native Runtime Package 2.2", "source": "programs", "vendor": "Microsoft Corporation", "version": "2.2.28604.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application", "name": "Microsoft Edge", "source": "programs", "vendor": "Microsoft Corporation", "version": "138.0.3351.65"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files (x86)\\Microsoft\\EdgeWebView\\Application", "name": "Microsoft Edge WebView2 Runtime", "source": "programs", "vendor": "Microsoft Corporation", "version": "137.0.3296.93"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.Services.Store.Engagement_10.0.23012.0_x64__8wekyb3d8bbwe", "name": "Microsoft Engagement Framework", "source": "programs", "vendor": "Microsoft Corporation", "version": "10.0.23012.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\Microsoft Office", "name": "Microsoft Office Home and Student 2019 - en-us", "source": "programs", "vendor": "Microsoft Corporation", "version": "16.0.18925.20138"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.WindowsStore_22505.1401.17.0_x64__8wekyb3d8bbwe", "name": "Microsoft Store", "source": "programs", "vendor": "Microsoft Corporation", "version": "22505.1401.17.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\MSTeams_25153.1010.3727.5483_x64__8wekyb3d8bbwe", "name": "Microsoft Teams", "source": "programs", "vendor": "Microsoft", "version": "25153.1010.3727.5483"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Windows\\System32\\ieframe.dll", "name": "Microsoft Url Search Hook", "source": "ie_extensions", "vendor": "", "version": "11.0.26100.4343"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.VCLibs.140.00.UWPDesktop_14.0.33519.0_x64__8wekyb3d8bbwe", "name": "Microsoft Visual C++ 2015 UWP Desktop Runtime Package", "source": "programs", "vendor": "Microsoft Platform Extensions", "version": "14.0.33519.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.VCLibs.140.00_14.0.33519.0_x64__8wekyb3d8bbwe", "name": "Microsoft Visual C++ 2015 UWP Runtime Package", "source": "programs", "vendor": "Microsoft Platform Extensions", "version": "14.0.33519.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Users\\zach\\AppData\\Local\\Programs\\Microsoft VS Code\\", "name": "Microsoft Visual Studio Code (User)", "source": "programs", "vendor": "Microsoft Corporation", "version": "1.101.2"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "\"C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\"", "name": "Microsoft Visual Studio Installer", "source": "programs", "vendor": "Microsoft Corporation", "version": "3.12.2320.19252"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Windows\\SystemApps\\Microsoft.AAD.BrokerPlugin_cw5n1h2txyewy", "name": "Microsoft.AAD.BrokerPlugin", "source": "programs", "vendor": "CN=Microsoft Windows, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", "version": "1000.19580.1000.2"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.AV1VideoExtension_1.3.20.0_x64__8wekyb3d8bbwe", "name": "Microsoft.AV1VideoExtension", "source": "programs", "vendor": "Microsoft Corporation", "version": "1.3.20.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.AVCEncoderVideoExtension_1.1.17.0_x64__8wekyb3d8bbwe", "name": "Microsoft.AVCEncoderVideoExtension", "source": "programs", "vendor": "Microsoft Corporation", "version": "1.1.17.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Windows\\SystemApps\\Microsoft.AccountsControl_cw5n1h2txyewy", "name": "Microsoft.AccountsControl", "source": "programs", "vendor": "CN=Microsoft Windows, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", "version": "10.0.26100.1"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.ApplicationCompatibilityEnhancements_1.2411.16.0_x64__8wekyb3d8bbwe", "name": "Microsoft.ApplicationCompatibilityEnhancements", "source": "programs", "vendor": "Microsoft Corporation", "version": "1.2411.16.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Windows\\SystemApps\\Microsoft.AsyncTextService_8wekyb3d8bbwe", "name": "Microsoft.AsyncTextService", "source": "programs", "vendor": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", "version": "10.0.26100.1"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.BingSearch_1.1.34.0_x64__8wekyb3d8bbwe", "name": "Microsoft.BingSearch", "source": "programs", "vendor": "Microsoft Corporation", "version": "1.1.34.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Windows\\SystemApps\\Microsoft.BioEnrollment_cw5n1h2txyewy", "name": "Microsoft.BioEnrollment", "source": "programs", "vendor": "CN=Microsoft Windows, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", "version": "10.0.19587.1000"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.CommandPalette_0.2.1.0_x64__8wekyb3d8bbwe", "name": "Microsoft.CommandPalette", "source": "programs", "vendor": "Microsoft Corporation", "version": "0.2.1.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Windows\\SystemApps\\microsoft.creddialoghost_cw5n1h2txyewy", "name": "Microsoft.CredDialogHost", "source": "programs", "vendor": "CN=Microsoft Windows, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", "version": "10.0.19595.1001"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.DesktopAppInstaller_1.26.400.0_x64__8wekyb3d8bbwe", "name": "Microsoft.DesktopAppInstaller", "source": "programs", "vendor": "Microsoft Corporation", "version": "1.26.400.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\WINDOWS\\SystemApps\\Microsoft.ECApp_8wekyb3d8bbwe", "name": "Microsoft.ECApp", "source": "programs", "vendor": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", "version": "10.0.26100.4061"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.Edge.GameAssist_1.0.3336.0_x64__8wekyb3d8bbwe", "name": "Microsoft.Edge.GameAssist", "source": "programs", "vendor": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", "version": "1.0.3336.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.GetHelp_10.2409.22951.0_x64__8wekyb3d8bbwe", "name": "Microsoft.GetHelp", "source": "programs", "vendor": "Microsoft Corporation", "version": "10.2409.22951.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.HEIFImageExtension_1.2.20.0_x64__8wekyb3d8bbwe", "name": "Microsoft.HEIFImageExtension", "source": "programs", "vendor": "Microsoft Corporation", "version": "1.2.20.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Windows\\SystemApps\\Microsoft.LockApp_cw5n1h2txyewy", "name": "Microsoft.LockApp", "source": "programs", "vendor": "CN=Microsoft Windows, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", "version": "10.0.26100.4202"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.MPEG2VideoExtension_1.2.10.0_x64__8wekyb3d8bbwe", "name": "Microsoft.MPEG2VideoExtension", "source": "programs", "vendor": "Microsoft Corporation", "version": "1.2.10.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.MSPaint_6.2410.13017.0_x64__8wekyb3d8bbwe", "name": "Microsoft.MSPaint", "source": "programs", "vendor": "Microsoft Corporation", "version": "6.2410.13017.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Windows\\SystemApps\\Microsoft.MicrosoftEdgeDevToolsClient_8wekyb3d8bbwe", "name": "Microsoft.MicrosoftEdgeDevToolsClient", "source": "programs", "vendor": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", "version": "1000.25128.1000.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.Paint_11.2504.531.0_x64__8wekyb3d8bbwe", "name": "Microsoft.Paint", "source": "programs", "vendor": "Microsoft Corporation", "version": "11.2504.531.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.People_10.2202.100.0_x64__8wekyb3d8bbwe", "name": "Microsoft.People", "source": "programs", "vendor": "Microsoft Corporation", "version": "10.2202.100.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.PowerAutomateDesktop_1.0.1420.0_x64__8wekyb3d8bbwe", "name": "Microsoft.PowerAutomateDesktop", "source": "programs", "vendor": "Microsoft Corporation", "version": "1.0.1420.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.RawImageExtension_2.5.5.0_x64__8wekyb3d8bbwe", "name": "Microsoft.RawImageExtension", "source": "programs", "vendor": "Microsoft Corporation", "version": "2.5.5.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.ScreenSketch_11.2504.42.0_x64__8wekyb3d8bbwe", "name": "Microsoft.ScreenSketch", "source": "programs", "vendor": "Microsoft Corporation", "version": "11.2504.42.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.SecHealthUI_1000.27840.1000.0_x64__8wekyb3d8bbwe", "name": "Microsoft.SecHealthUI", "source": "programs", "vendor": "Microsoft Corporation", "version": "1000.27840.1000.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.StorePurchaseApp_22505.1401.0.0_x64__8wekyb3d8bbwe", "name": "Microsoft.StorePurchaseApp", "source": "programs", "vendor": "Microsoft Corporation", "version": "22505.1401.0.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.UI.Xaml.2.0_2.1810.18004.0_x64__8wekyb3d8bbwe", "name": "Microsoft.UI.Xaml.2.0", "source": "programs", "vendor": "Microsoft Platform Extensions", "version": "2.1810.18004.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.UI.Xaml.2.7_7.2409.9001.0_x64__8wekyb3d8bbwe", "name": "Microsoft.UI.Xaml.2.7", "source": "programs", "vendor": "Microsoft Platform Extensions", "version": "7.2409.9001.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.UI.Xaml.2.8_8.2310.30001.0_x64__8wekyb3d8bbwe", "name": "Microsoft.UI.Xaml.2.8", "source": "programs", "vendor": "Microsoft Platform Extensions", "version": "8.2310.30001.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\WINDOWS\\SystemApps\\Microsoft.UI.Xaml.CBS_8wekyb3d8bbwe", "name": "Microsoft.UI.Xaml.CBS", "source": "programs", "vendor": "Microsoft Platform Extensions", "version": "9.2311.10002.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.VP9VideoExtensions_1.2.6.0_x64__8wekyb3d8bbwe", "name": "Microsoft.VP9VideoExtensions", "source": "programs", "vendor": "Microsoft Corporation", "version": "1.2.6.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.WebMediaExtensions_1.2.14.0_x64__8wekyb3d8bbwe", "name": "Microsoft.WebMediaExtensions", "source": "programs", "vendor": "Microsoft Corporation", "version": "1.2.14.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.WebpImageExtension_1.2.10.0_x64__8wekyb3d8bbwe", "name": "Microsoft.WebpImageExtension", "source": "programs", "vendor": "Microsoft Corporation", "version": "1.2.10.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Windows\\SystemApps\\Microsoft.Win32WebViewHost_cw5n1h2txyewy", "name": "Microsoft.Win32WebViewHost", "source": "programs", "vendor": "CN=Microsoft Windows, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", "version": "10.0.26100.1"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Windows\\SystemApps\\Microsoft.Windows.AppRep.ChxApp_cw5n1h2txyewy", "name": "Microsoft.Windows.Apprep.ChxApp", "source": "programs", "vendor": "CN=Microsoft Windows, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", "version": "1000.25128.1000.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.WindowsCalculator_11.2502.2.0_x64__8wekyb3d8bbwe", "name": "Microsoft.WindowsCalculator", "source": "programs", "vendor": "Microsoft Corporation", "version": "11.2502.2.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.WindowsCamera_2025.2505.2.0_x64__8wekyb3d8bbwe", "name": "Microsoft.WindowsCamera", "source": "programs", "vendor": "Microsoft Corporation", "version": "2025.2505.2.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.WindowsNotepad_11.2504.62.0_x64__8wekyb3d8bbwe", "name": "Microsoft.WindowsNotepad", "source": "programs", "vendor": "Microsoft Corporation", "version": "11.2504.62.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.WindowsTerminal_1.22.11141.0_x64__8wekyb3d8bbwe", "name": "Microsoft.WindowsTerminal", "source": "programs", "vendor": "Microsoft Corporation", "version": "1.22.11141.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Windows\\SystemApps\\Microsoft.XboxGameCallableUI_cw5n1h2txyewy", "name": "Microsoft.XboxGameCallableUI", "source": "programs", "vendor": "CN=Microsoft Windows, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", "version": "1000.25128.1000.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.XboxIdentityProvider_12.115.1001.0_x64__8wekyb3d8bbwe", "name": "Microsoft.XboxIdentityProvider", "source": "programs", "vendor": "Microsoft Corporation", "version": "12.115.1001.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.XboxSpeechToTextOverlay_1.97.17002.0_neutral_split.scale-125_8wekyb3d8bbwe", "name": "Microsoft.XboxSpeechToTextOverlay", "source": "programs", "vendor": "Microsoft Corporation", "version": "1.97.17002.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.YourPhone_1.25052.76.0_x64__8wekyb3d8bbwe", "name": "Microsoft.YourPhone", "source": "programs", "vendor": "Microsoft Corporation", "version": "1.25052.76.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.ZuneMusic_11.2505.2.0_x64__8wekyb3d8bbwe", "name": "Microsoft.ZuneMusic", "source": "programs", "vendor": "Microsoft Corporation", "version": "11.2505.2.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\WINDOWS\\SystemApps\\SxS\\MicrosoftWindows.54792954.Filons_cw5n1h2txyewy", "name": "MicrosoftWindows.54792954.Filons", "source": "programs", "vendor": "Microsoft Windows", "version": "1000.26100.4351.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\WINDOWS\\SystemApps\\SxS\\MicrosoftWindows.56978801.Voiess_cw5n1h2txyewy", "name": "MicrosoftWindows.56978801.Voiess", "source": "programs", "vendor": "Microsoft Windows", "version": "1000.26100.4351.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\WINDOWS\\SystemApps\\SxS\\MicrosoftWindows.57058570.Speion_cw5n1h2txyewy", "name": "MicrosoftWindows.57058570.Speion", "source": "programs", "vendor": "Microsoft Windows", "version": "1000.26100.4351.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\WINDOWS\\SystemApps\\SxS\\MicrosoftWindows.57074914.Livtop_cw5n1h2txyewy", "name": "MicrosoftWindows.57074914.Livtop", "source": "programs", "vendor": "Microsoft Windows", "version": "1000.26100.4351.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\WINDOWS\\SystemApps\\MicrosoftWindows.Client.CBS_cw5n1h2txyewy", "name": "MicrosoftWindows.Client.CBS", "source": "programs", "vendor": "Microsoft Windows", "version": "1000.26100.107.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\WINDOWS\\SystemApps\\MicrosoftWindows.Client.Core_cw5n1h2txyewy", "name": "MicrosoftWindows.Client.Core", "source": "programs", "vendor": "Microsoft Windows", "version": "1000.26100.46.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\WINDOWS\\SystemApps\\MicrosoftWindows.Client.CoreAI_cw5n1h2txyewy", "name": "MicrosoftWindows.Client.CoreAI", "source": "programs", "vendor": "Microsoft Windows", "version": "1000.26100.4351.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\WINDOWS\\SystemApps\\MicrosoftWindows.Client.FileExp_cw5n1h2txyewy", "name": "MicrosoftWindows.Client.FileExp", "source": "programs", "vendor": "Microsoft Windows", "version": "1000.26100.3.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Windows\\SystemApps\\MicrosoftWindows.Client.OOBE_cw5n1h2txyewy", "name": "MicrosoftWindows.Client.OOBE", "source": "programs", "vendor": "Microsoft Windows", "version": "1000.26100.7.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Windows\\SystemApps\\MicrosoftWindows.Client.Photon_cw5n1h2txyewy", "name": "MicrosoftWindows.Client.Photon", "source": "programs", "vendor": "Microsoft Windows", "version": "1000.26100.8.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\MicrosoftWindows.CrossDevice_1.25061.25.0_x64__cw5n1h2txyewy", "name": "MicrosoftWindows.CrossDevice", "source": "programs", "vendor": "Microsoft Windows", "version": "1.25061.25.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\Mozilla Firefox", "name": "Mozilla Firefox (x64 en-US)", "source": "programs", "vendor": "Mozilla", "version": "139.0.4"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.OutlookForWindows_1.2025.129.300_x64__8wekyb3d8bbwe", "name": "Outlook for Windows", "source": "programs", "vendor": "Microsoft Corporation", "version": "1.2025.129.300"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Windows\\SystemApps\\Microsoft.Windows.PinningConfirmationDialog_cw5n1h2txyewy", "name": "PinningConfirmationDialog", "source": "programs", "vendor": "Microsoft Corporation", "version": "1000.25140.1001.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\PowerToys\\", "name": "PowerToys (Preview)", "source": "programs", "vendor": "Microsoft Corporation", "version": "0.91.1"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "PowerToys (Preview) x64", "source": "programs", "vendor": "Microsoft Corporation", "version": "0.91.1"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Strawberry\\", "name": "Strawberry Perl (64-bit)", "source": "programs", "vendor": "strawberryperl.com project", "version": "5.38.1"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Windows\\SystemApps\\MicrosoftWindows.UndockedDevKit_cw5n1h2txyewy", "name": "UDK Package", "source": "programs", "vendor": "Microsoft Corporation", "version": "10.0.26100.1"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community", "name": "Visual Studio Community 2022", "source": "programs", "vendor": "Microsoft Corporation", "version": "17.12.4"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.WidgetsPlatformRuntime_1.6.9.0_x64__8wekyb3d8bbwe", "name": "Widgets Platform Runtime", "source": "programs", "vendor": "Microsoft Corporation", "version": "1.6.9.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "WinAppDeploy", "source": "programs", "vendor": "Microsoft Corporation", "version": "10.1.20348.1"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\MicrosoftWindows.Client.WebExperience_525.15301.20.0_x64__cw5n1h2txyewy", "name": "Windows Web Experience Pack", "source": "programs", "vendor": "Microsoft Windows", "version": "525.15301.20.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Windows\\SystemApps\\Windows.CBSPreview_cw5n1h2txyewy", "name": "Windows.CBSPreview", "source": "programs", "vendor": "CN=Microsoft Windows, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", "version": "10.0.19580.1000"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Windows\\SystemApps\\Windows.PrintDialog_cw5n1h2txyewy", "name": "Windows.PrintDialog", "source": "programs", "vendor": "CN=Microsoft Windows, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", "version": "6.2.3.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.WindowsAppRuntime.1.3_3000.934.1904.0_x86__8wekyb3d8bbwe", "name": "WindowsAppRuntime.1.3", "source": "programs", "vendor": "Microsoft Corporation", "version": "3000.934.1904.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.WindowsAppRuntime.1.4_4000.1136.2333.0_x64__8wekyb3d8bbwe", "name": "WindowsAppRuntime.1.4", "source": "programs", "vendor": "Microsoft Corporation", "version": "4000.1136.2333.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.WindowsAppRuntime.1.5_5001.373.1736.0_x64__8wekyb3d8bbwe", "name": "WindowsAppRuntime.1.5", "source": "programs", "vendor": "Microsoft Corporation", "version": "5001.373.1736.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.WindowsAppRuntime.1.6_6000.486.517.0_x64__8wekyb3d8bbwe", "name": "WindowsAppRuntime.1.6", "source": "programs", "vendor": "Microsoft Corporation", "version": "6000.486.517.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.WindowsAppRuntime.1.7_7000.522.1444.0_x64__8wekyb3d8bbwe", "name": "WindowsAppRuntime.1.7", "source": "programs", "vendor": "Microsoft Corporation", "version": "7000.522.1444.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.GamingApp_2506.1001.20.0_x64__8wekyb3d8bbwe", "name": "Xbox", "source": "programs", "vendor": "Microsoft Corporation", "version": "2506.1001.20.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.XboxGameOverlay_1.54.4001.0_x64__8wekyb3d8bbwe", "name": "Xbox Game Bar Plugin", "source": "programs", "vendor": "Microsoft Corporation", "version": "1.54.4001.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\Microsoft.Xbox.TCUI_1.24.10001.0_x64__8wekyb3d8bbwe", "name": "Xbox TCUI", "source": "programs", "vendor": "Microsoft Corporation", "version": "1.24.10001.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Windows\\SystemApps\\Microsoft.Windows.FileExplorer_cw5n1h2txyewy", "name": "c5e2524a-ea46-4f67-841f-6a9465d9d515", "source": "programs", "vendor": "Microsoft Corporation", "version": "10.0.26100.1"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Program Files\\WindowsApps\\microsoft.windowscommunicationsapps_16005.14326.22342.0_x64__8wekyb3d8bbwe", "name": "microsoft.windowscommunicationsapps", "source": "programs", "vendor": "Microsoft Corporation", "version": "16005.14326.22342.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "vcpp_crt.redist.clickonce", "source": "programs", "vendor": "Microsoft Corporation", "version": "14.29.30157"},
|
||
{"extension_for": "", "bundle_identifier": "", "extension_id": "99b17261-8f6e-45f0-9ad5-a69c6f509a4f", "installed_path": "/c:/Users/zach/.vscode/extensions/ms-vscode.cpptools-themes-2.0.0", "last_opened_at": "", "name": "ms-vscode.cpptools-themes", "source": "vscode_extensions", "vendor": "Microsoft", "version": "2.0.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "C:\\Users\\zach\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\pip-23.2.1.dist-info", "name": "pip", "source": "python_packages", "vendor": "", "version": "23.2.1"},
|
||
}
|
||
softwareWithLastUsed := processFunc(softwareResults, prefetchResults)
|
||
|
||
for _, software := range softwareWithLastUsed {
|
||
if software["source"] != "programs" {
|
||
// Last opened at should only be set for programs
|
||
assert.Equal(t, "", software["last_opened_at"])
|
||
}
|
||
|
||
if software["installed_path"] == "C:\\Windows\\System32\\" {
|
||
assert.Equal(t, "", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "" {
|
||
assert.Equal(t, "", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "Strawberry Perl (64-bit)" {
|
||
assert.Equal(t, "1749664524", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "Microsoft.MSPaint" {
|
||
assert.Equal(t, "1751739848", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "Microsoft.Paint" {
|
||
assert.Equal(t, "1751739842", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "Microsoft Visual Studio Code (User)" {
|
||
assert.Equal(t, "1751756772", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "CMake" {
|
||
assert.Equal(t, "1751756665", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "Mozilla Firefox (x64 en-US)" {
|
||
assert.Equal(t, "1751755087", software["last_opened_at"])
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestWindowsAcrobatDC(t *testing.T) {
|
||
processFunc := SoftwareOverrideQueries["windows_acrobat_dc"].SoftwareProcessResults
|
||
softwareResults := []map[string]string{
|
||
{"name": "Adobe Acrobat Reader 64-bit"},
|
||
{"name": "Adobe Acrobat 2017"},
|
||
{"name": "Acrobat Reader"},
|
||
}
|
||
|
||
testCases := []struct {
|
||
name string
|
||
registryResults []map[string]string
|
||
expected []map[string]string
|
||
}{
|
||
{
|
||
name: "no registry results",
|
||
registryResults: nil,
|
||
expected: softwareResults,
|
||
},
|
||
{
|
||
name: "registry results",
|
||
registryResults: []map[string]string{
|
||
{"present": ""},
|
||
},
|
||
expected: []map[string]string{
|
||
{"name": "Adobe Acrobat Reader DC 64-bit"},
|
||
{"name": "Adobe Acrobat DC 2017"},
|
||
{"name": "Acrobat Reader DC"},
|
||
},
|
||
},
|
||
}
|
||
|
||
for _, testCase := range testCases {
|
||
t.Run(testCase.name, func(t *testing.T) {
|
||
result := processFunc(softwareResults, testCase.registryResults)
|
||
require.Equal(t, testCase.expected, result)
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestTPMPinSetVerifyIngest(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
host *fleet.Host
|
||
rows []map[string]string
|
||
pinSet *bool
|
||
}{
|
||
{
|
||
name: "nil host",
|
||
host: nil,
|
||
rows: []map[string]string{
|
||
{"host": "something", "criteria": "1"},
|
||
},
|
||
},
|
||
{
|
||
name: "empty uuid",
|
||
host: &fleet.Host{
|
||
ID: 1,
|
||
},
|
||
rows: []map[string]string{{"host": "something", "criteria": "1"}},
|
||
},
|
||
{
|
||
name: "no rows - pin not set",
|
||
host: &fleet.Host{
|
||
ID: 1,
|
||
UUID: "test-uuid",
|
||
},
|
||
rows: []map[string]string{},
|
||
pinSet: ptr.Bool(false),
|
||
},
|
||
{
|
||
name: "with rows - pin set",
|
||
host: &fleet.Host{
|
||
ID: 1,
|
||
UUID: "test-uuid",
|
||
},
|
||
rows: []map[string]string{
|
||
{"host": "Mordor", "criteria": "1"},
|
||
},
|
||
pinSet: ptr.Bool(true),
|
||
},
|
||
{
|
||
name: "multiple rows - pin set",
|
||
host: &fleet.Host{
|
||
ID: 1,
|
||
UUID: "test-uuid",
|
||
},
|
||
rows: []map[string]string{
|
||
{"host": "Mordor", "criteria": "1"},
|
||
{"host": "Mordor", "criteria": "1"},
|
||
},
|
||
pinSet: ptr.Bool(true),
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
ds := new(mock.Store)
|
||
|
||
var setPinCalled bool
|
||
ds.SetOrUpdateHostDiskTpmPINFunc = func(ctx context.Context, hostID uint, pinSet bool) error {
|
||
setPinCalled = true
|
||
require.Equal(t, *tt.pinSet, pinSet)
|
||
require.Equal(t, tt.host.ID, hostID)
|
||
return nil
|
||
}
|
||
|
||
ingestFunc := tpmPINQueries["tpm_pin_set_verify"].DirectIngestFunc
|
||
|
||
require.NoError(t, ingestFunc(t.Context(), slog.New(slog.DiscardHandler), tt.host, ds, tt.rows))
|
||
require.Equal(t, setPinCalled, tt.pinSet != nil)
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestTPMPinConfigVerifyDirectIngest(t *testing.T) {
|
||
logger := slog.New(slog.DiscardHandler)
|
||
ctx := t.Context()
|
||
|
||
tests := []struct {
|
||
name string
|
||
host *fleet.Host
|
||
rows []map[string]string
|
||
wantCmd bool
|
||
wantError bool
|
||
}{
|
||
{
|
||
name: "nil host",
|
||
host: nil,
|
||
},
|
||
{
|
||
name: "empty UUID host",
|
||
host: &fleet.Host{UUID: ""},
|
||
},
|
||
{
|
||
name: "too many rows",
|
||
host: &fleet.Host{
|
||
UUID: "test-uuid",
|
||
ID: 1,
|
||
},
|
||
rows: []map[string]string{
|
||
{"data": strconv.Itoa(microsoft_mdm.PolicyOptDropdownOptional)},
|
||
{"data": strconv.Itoa(microsoft_mdm.PolicyOptDropdownOptional)},
|
||
},
|
||
wantError: true,
|
||
},
|
||
{
|
||
name: "no rows - requires command",
|
||
host: &fleet.Host{
|
||
UUID: "test-uuid",
|
||
ID: 1,
|
||
},
|
||
rows: []map[string]string{},
|
||
wantCmd: true,
|
||
},
|
||
{
|
||
name: "disallowed state - requires command",
|
||
host: &fleet.Host{
|
||
UUID: "test-uuid",
|
||
ID: 1,
|
||
},
|
||
rows: []map[string]string{
|
||
{"data": strconv.Itoa(microsoft_mdm.PolicyOptDropdownDisallowed)},
|
||
},
|
||
wantCmd: true,
|
||
},
|
||
{
|
||
name: "policy set to optinal",
|
||
host: &fleet.Host{
|
||
UUID: "test-uuid",
|
||
ID: 1,
|
||
},
|
||
rows: []map[string]string{
|
||
{"data": strconv.Itoa(microsoft_mdm.PolicyOptDropdownOptional)},
|
||
},
|
||
},
|
||
{
|
||
name: "policy set to required",
|
||
host: &fleet.Host{
|
||
UUID: "test-uuid",
|
||
ID: 1,
|
||
},
|
||
rows: []map[string]string{
|
||
{"data": strconv.Itoa(microsoft_mdm.PolicyOptDropdownRequired)},
|
||
},
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
ds := new(mock.Store)
|
||
|
||
cmdInserted := false
|
||
if tt.wantCmd {
|
||
ds.MDMWindowsInsertCommandForHostsFunc = func(
|
||
ctx context.Context,
|
||
hostUUIDs []string,
|
||
cmd *fleet.MDMWindowsCommand,
|
||
) error {
|
||
cmdInserted = true
|
||
require.Equal(t, []string{tt.host.UUID}, hostUUIDs)
|
||
require.NotNil(t, cmd)
|
||
return nil
|
||
}
|
||
}
|
||
|
||
ingestFunc := tpmPINQueries["tpm_pin_config_verify"].DirectIngestFunc
|
||
err := ingestFunc(ctx, logger, tt.host, ds, tt.rows)
|
||
require.Equal(t, tt.wantError, err != nil)
|
||
require.Equal(t, cmdInserted, tt.wantCmd)
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestDebLastOpenedAt(t *testing.T) {
|
||
processFunc := SoftwareOverrideQueries["deb_last_opened_at"].SoftwareProcessResults
|
||
debPackageResults := []map[string]string{
|
||
{"package": "accountsservice", "last_opened_at": "1753287489"},
|
||
{"package": "acl", "last_opened_at": "1752178409"},
|
||
{"package": "adduser", "last_opened_at": "1753287887"},
|
||
{"package": "alsa-base", "last_opened_at": "1752178572"},
|
||
{"package": "alsa-utils", "last_opened_at": "1753806108"},
|
||
{"package": "anacron", "last_opened_at": "1754439481"},
|
||
{"package": "apg", "last_opened_at": "1752178498"},
|
||
{"package": "apparmor", "last_opened_at": "1753978475"},
|
||
{"package": "apport", "last_opened_at": "1754439481"},
|
||
{"package": "apport-core-dump-handler", "last_opened_at": "1752791628"},
|
||
{"package": "apport-gtk", "last_opened_at": "1752791824"},
|
||
{"package": "appstream", "last_opened_at": "1754361022"},
|
||
{"package": "apt", "last_opened_at": "1754439481"},
|
||
{"package": "apt-file", "last_opened_at": "1753293736"},
|
||
{"package": "apt-utils", "last_opened_at": "1753978482"},
|
||
{"package": "aptdaemon", "last_opened_at": "1752791767"},
|
||
{"package": "aspell", "last_opened_at": "1752178619"},
|
||
{"package": "at-spi2-core", "last_opened_at": "1753806242"},
|
||
{"package": "avahi-daemon", "last_opened_at": "1753806108"},
|
||
{"package": "baobab", "last_opened_at": "1753806594"},
|
||
}
|
||
softwareResults := []map[string]string{
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "accountsservice", "source": "deb_packages", "vendor": "", "version": "23.13.9-2ubuntu6"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "acl", "source": "deb_packages", "vendor": "", "version": "2.3.2-1build1.1"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "adduser", "source": "deb_packages", "vendor": "", "version": "3.137ubuntu1"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "adwaita-icon-theme", "source": "deb_packages", "vendor": "", "version": "46.0-1"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "alsa-base", "source": "deb_packages", "vendor": "", "version": "1.0.25+dfsg-0ubuntu7"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "alsa-topology-conf", "source": "deb_packages", "vendor": "", "version": "1.2.5.1-2"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "alsa-ucm-conf", "source": "deb_packages", "vendor": "", "version": "1.2.10-1ubuntu5.7"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "alsa-utils", "source": "deb_packages", "vendor": "", "version": "1.2.9-1ubuntu5"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "anacron", "source": "deb_packages", "vendor": "", "version": "2.3-39ubuntu2"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "apg", "source": "deb_packages", "vendor": "", "version": "2.2.3.dfsg.1-5build3"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "apparmor", "source": "deb_packages", "vendor": "", "version": "4.0.1really4.0.1-0ubuntu0.24.04.4"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "apport", "source": "deb_packages", "vendor": "", "version": "2.28.1-0ubuntu3.8"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "apport-core-dump-handler", "source": "deb_packages", "vendor": "", "version": "2.28.1-0ubuntu3.8"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "apport-gtk", "source": "deb_packages", "vendor": "", "version": "2.28.1-0ubuntu3.8"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "apport-symptoms", "source": "deb_packages", "vendor": "", "version": "0.25"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "appstream", "source": "deb_packages", "vendor": "", "version": "1.0.2-1build6"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "apt", "source": "deb_packages", "vendor": "", "version": "2.8.3"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "apt-config-icons", "source": "deb_packages", "vendor": "", "version": "1.0.2-1build6"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "apt-config-icons-hidpi", "source": "deb_packages", "vendor": "", "version": "1.0.2-1build6"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "apt-file", "source": "deb_packages", "vendor": "", "version": "3.3"},
|
||
// Add some non-deb_packages software to test filtering
|
||
{"extension_for": "firefox", "extension_id": "test-extension", "installed_path": "", "name": "Firefox Extension", "source": "firefox_addons", "vendor": "", "version": "1.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "chrome-extension", "source": "chrome_extensions", "vendor": "", "version": "1.0"},
|
||
}
|
||
softwareWithLastUsed := processFunc(softwareResults, debPackageResults)
|
||
|
||
for _, software := range softwareWithLastUsed {
|
||
if software["source"] != "deb_packages" {
|
||
// Last opened at should only be set for deb_packages
|
||
assert.Equal(t, "", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "accountsservice" {
|
||
assert.Equal(t, "1753287489", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "acl" {
|
||
assert.Equal(t, "1752178409", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "adduser" {
|
||
assert.Equal(t, "1753287887", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "alsa-base" {
|
||
assert.Equal(t, "1752178572", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "alsa-utils" {
|
||
assert.Equal(t, "1753806108", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "anacron" {
|
||
assert.Equal(t, "1754439481", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "apg" {
|
||
assert.Equal(t, "1752178498", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "apparmor" {
|
||
assert.Equal(t, "1753978475", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "apport" {
|
||
assert.Equal(t, "1754439481", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "apport-core-dump-handler" {
|
||
assert.Equal(t, "1752791628", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "apport-gtk" {
|
||
assert.Equal(t, "1752791824", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "appstream" {
|
||
assert.Equal(t, "1754361022", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "apt" {
|
||
assert.Equal(t, "1754439481", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "apt-file" {
|
||
assert.Equal(t, "1753293736", software["last_opened_at"])
|
||
}
|
||
|
||
// Test packages that don't have last_opened_at data
|
||
if software["name"] == "adwaita-icon-theme" {
|
||
assert.Equal(t, "", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "alsa-topology-conf" {
|
||
assert.Equal(t, "", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "alsa-ucm-conf" {
|
||
assert.Equal(t, "", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "apport-symptoms" {
|
||
assert.Equal(t, "", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "apt-config-icons" {
|
||
assert.Equal(t, "", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "apt-config-icons-hidpi" {
|
||
assert.Equal(t, "", software["last_opened_at"])
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestRpmLastOpenedAt(t *testing.T) {
|
||
processFunc := SoftwareOverrideQueries["rpm_last_opened_at"].SoftwareProcessResults
|
||
rpmPackageResults := []map[string]string{
|
||
{"package": "bash", "last_opened_at": "1753287489"},
|
||
{"package": "coreutils", "last_opened_at": "1752178409"},
|
||
{"package": "curl", "last_opened_at": "1753287887"},
|
||
{"package": "firewalld", "last_opened_at": "1752178572"},
|
||
{"package": "git", "last_opened_at": "1753806108"},
|
||
{"package": "httpd", "last_opened_at": "1754439481"},
|
||
{"package": "java-11-openjdk", "last_opened_at": "1752178498"},
|
||
{"package": "kernel", "last_opened_at": "1753978475"},
|
||
{"package": "libcurl", "last_opened_at": "1754439481"},
|
||
{"package": "mysql-server", "last_opened_at": "1752791628"},
|
||
{"package": "nginx", "last_opened_at": "1752791824"},
|
||
{"package": "nodejs", "last_opened_at": "1754361022"},
|
||
{"package": "openssh-server", "last_opened_at": "1754439481"},
|
||
{"package": "postgresql", "last_opened_at": "1753293736"},
|
||
{"package": "python3", "last_opened_at": "1753978482"},
|
||
{"package": "redis", "last_opened_at": "1752791767"},
|
||
{"package": "systemd", "last_opened_at": "1752178619"},
|
||
{"package": "vim", "last_opened_at": "1753806242"},
|
||
{"package": "wget", "last_opened_at": "1753806108"},
|
||
{"package": "yum", "last_opened_at": "1753806594"},
|
||
}
|
||
softwareResults := []map[string]string{
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "bash", "source": "rpm_packages", "vendor": "", "version": "4.2.46-35.el7_9"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "coreutils", "source": "rpm_packages", "vendor": "", "version": "8.22-24.el7_9.2"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "curl", "source": "rpm_packages", "vendor": "", "version": "7.29.0-59.el7_9.1"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "firewalld", "source": "rpm_packages", "vendor": "", "version": "0.6.3-13.el7_9"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "git", "source": "rpm_packages", "vendor": "", "version": "1.8.3.1-25.el7_9"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "httpd", "source": "rpm_packages", "vendor": "", "version": "2.4.6-97.el7_9.1"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "java-11-openjdk", "source": "rpm_packages", "vendor": "", "version": "11.0.21.0.9-1.el7_9"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "kernel", "source": "rpm_packages", "vendor": "", "version": "3.10.0-1160.105.1.el7"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "libcurl", "source": "rpm_packages", "vendor": "", "version": "7.29.0-59.el7_9.1"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "mysql-server", "source": "rpm_packages", "vendor": "", "version": "5.7.44-1.el7"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "nginx", "source": "rpm_packages", "vendor": "", "version": "1.20.1-9.el7"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "nodejs", "source": "rpm_packages", "vendor": "", "version": "16.20.2-1.el7"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "openssh-server", "source": "rpm_packages", "vendor": "", "version": "7.4p1-23.el7_9"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "postgresql", "source": "rpm_packages", "vendor": "", "version": "13.14-1.el7_9"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "python3", "source": "rpm_packages", "vendor": "", "version": "3.6.8-18.el7_9.1"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "redis", "source": "rpm_packages", "vendor": "", "version": "5.0.3-1.el7"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "systemd", "source": "rpm_packages", "vendor": "", "version": "219-78.el7_9.8"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "vim", "source": "rpm_packages", "vendor": "", "version": "7.4.629-8.el7_9"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "wget", "source": "rpm_packages", "vendor": "", "version": "1.14-18.el7_6.1"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "yum", "source": "rpm_packages", "vendor": "", "version": "3.4.3-168.el7_9"},
|
||
// Add some packages that don't have last_opened_at data
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "glibc", "source": "rpm_packages", "vendor": "", "version": "2.17-325.el7_9"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "openssl", "source": "rpm_packages", "vendor": "", "version": "1.0.2k-26.el7_9"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "zlib", "source": "rpm_packages", "vendor": "", "version": "1.2.7-18.el7"},
|
||
// Add some non-rpm_packages software to test filtering
|
||
{"extension_for": "firefox", "extension_id": "test-extension", "installed_path": "", "name": "Firefox Extension", "source": "firefox_addons", "vendor": "", "version": "1.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "chrome-extension", "source": "chrome_extensions", "vendor": "", "version": "1.0"},
|
||
{"extension_for": "", "extension_id": "", "installed_path": "", "name": "deb-package", "source": "deb_packages", "vendor": "", "version": "1.0"},
|
||
}
|
||
softwareWithLastUsed := processFunc(softwareResults, rpmPackageResults)
|
||
|
||
for _, software := range softwareWithLastUsed {
|
||
if software["source"] != "rpm_packages" {
|
||
// Last opened at should only be set for rpm_packages
|
||
assert.Equal(t, "", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "bash" {
|
||
assert.Equal(t, "1753287489", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "coreutils" {
|
||
assert.Equal(t, "1752178409", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "curl" {
|
||
assert.Equal(t, "1753287887", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "firewalld" {
|
||
assert.Equal(t, "1752178572", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "git" {
|
||
assert.Equal(t, "1753806108", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "httpd" {
|
||
assert.Equal(t, "1754439481", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "java-11-openjdk" {
|
||
assert.Equal(t, "1752178498", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "kernel" {
|
||
assert.Equal(t, "1753978475", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "libcurl" {
|
||
assert.Equal(t, "1754439481", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "mysql-server" {
|
||
assert.Equal(t, "1752791628", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "nginx" {
|
||
assert.Equal(t, "1752791824", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "nodejs" {
|
||
assert.Equal(t, "1754361022", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "openssh-server" {
|
||
assert.Equal(t, "1754439481", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "postgresql" {
|
||
assert.Equal(t, "1753293736", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "python3" {
|
||
assert.Equal(t, "1753978482", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "redis" {
|
||
assert.Equal(t, "1752791767", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "systemd" {
|
||
assert.Equal(t, "1752178619", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "vim" {
|
||
assert.Equal(t, "1753806242", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "wget" {
|
||
assert.Equal(t, "1753806108", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "yum" {
|
||
assert.Equal(t, "1753806594", software["last_opened_at"])
|
||
}
|
||
|
||
// Test packages that don't have last_opened_at data
|
||
if software["name"] == "glibc" {
|
||
assert.Equal(t, "", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "openssl" {
|
||
assert.Equal(t, "", software["last_opened_at"])
|
||
}
|
||
|
||
if software["name"] == "zlib" {
|
||
assert.Equal(t, "", software["last_opened_at"])
|
||
}
|
||
}
|
||
}
|