mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
Added orbit_version, fleet_desktop_version, and scripts_enabled to host details. (#18123)
#17361 #17148 In GET fleet/hosts/:id response, added the following fields: - orbit_version - `orbit_version == null` means this agent is not an orbit agent - fleet_desktop_version - `fleet_desktop_version == null` means this agent is not an orbit agent or it is an older version which is not collecting the desktop version - `fleet_desktop_version == ""` means this agent is an orbit agent but does not have fleet desktop - scripts_enabled - `scripts_enabled == null` means this agent is not an orbit agent or it is an older version which is not collecting scripts_enabled In orbit_info table, added the following fields: - desktop_version - scripts_enabled Updated docs for orbit_info PR: https://github.com/fleetdm/fleet/pull/18135 Updated API docs: https://github.com/fleetdm/fleet/pull/17814 MDM lock/unlock/wipe error messages are not part of this PR. They will be in a separate PR. # Checklist for submitter - [x] Changes file added for user-visible changes in `changes/` or `orbit/changes/`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - [x] Added support on fleet's osquery simulator `cmd/osquery-perf` for new osquery data ingestion features. - [x] Added/updated tests - [x] If database migrations are included, checked table schema to confirm autoupdate - [x] Manual QA for all new/changed functionality - For Orbit and Fleet Desktop changes: - [x] Manual QA must be performed in the three main OSs, macOS, Windows and Linux. - [x] Auto-update manual QA, from released version of component to new version (see [tools/tuf/test](../tools/tuf/test/README.md)).
This commit is contained in:
parent
630b71875e
commit
3367b7e036
29 changed files with 324 additions and 32 deletions
1
changes/17362-orbit-and-desktop-version
Normal file
1
changes/17362-orbit-and-desktop-version
Normal file
|
|
@ -0,0 +1 @@
|
|||
In GET fleet/hosts/:id response, added orbit_version, fleet_desktop_version, and scripts_enabled fields.
|
||||
|
|
@ -18,6 +18,9 @@
|
|||
"uuid": "",
|
||||
"platform": "",
|
||||
"osquery_version": "",
|
||||
"orbit_version": null,
|
||||
"fleet_desktop_version": null,
|
||||
"scripts_enabled": null,
|
||||
"os_version": "",
|
||||
"build": "",
|
||||
"platform_like": "",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ spec:
|
|||
display_text: test_host
|
||||
display_name: test_host
|
||||
distributed_interval: 0
|
||||
fleet_desktop_version: null
|
||||
gigs_disk_space_available: 0
|
||||
gigs_total_disk_space: 0
|
||||
hardware_model: ""
|
||||
|
|
@ -40,6 +41,7 @@ spec:
|
|||
pending_action: ""
|
||||
server_url: null
|
||||
memory: 0
|
||||
orbit_version: null
|
||||
os_version: ""
|
||||
osquery_version: ""
|
||||
pack_stats: null
|
||||
|
|
@ -83,6 +85,7 @@ spec:
|
|||
primary_mac: ""
|
||||
refetch_requested: false
|
||||
refetch_critical_queries_until: null
|
||||
scripts_enabled: null
|
||||
seen_time: "0001-01-01T00:00:00Z"
|
||||
software_updated_at: "0001-01-01T00:00:00Z"
|
||||
status: offline
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@
|
|||
"uuid": "",
|
||||
"platform": "",
|
||||
"osquery_version": "",
|
||||
"orbit_version": null,
|
||||
"fleet_desktop_version": null,
|
||||
"scripts_enabled": null,
|
||||
"os_version": "",
|
||||
"build": "",
|
||||
"platform_like": "",
|
||||
|
|
@ -89,6 +92,9 @@
|
|||
"uuid": "",
|
||||
"platform": "",
|
||||
"osquery_version": "",
|
||||
"orbit_version": null,
|
||||
"fleet_desktop_version": null,
|
||||
"scripts_enabled": null,
|
||||
"os_version": "",
|
||||
"build": "",
|
||||
"platform_like": "",
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@
|
|||
"uuid": "",
|
||||
"platform": "",
|
||||
"osquery_version": "",
|
||||
"orbit_version": null,
|
||||
"fleet_desktop_version": null,
|
||||
"scripts_enabled": null,
|
||||
"os_version": "",
|
||||
"build": "",
|
||||
"platform_like": "",
|
||||
|
|
@ -90,6 +93,9 @@
|
|||
"uuid": "",
|
||||
"platform": "",
|
||||
"osquery_version": "",
|
||||
"orbit_version": null,
|
||||
"fleet_desktop_version": null,
|
||||
"scripts_enabled": null,
|
||||
"os_version": "",
|
||||
"build": "",
|
||||
"platform_like": "",
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ spec:
|
|||
detail_updated_at: "0001-01-01T00:00:00Z"
|
||||
display_text: test_host
|
||||
distributed_interval: 0
|
||||
fleet_desktop_version: null
|
||||
gigs_disk_space_available: 0
|
||||
gigs_total_disk_space: 0
|
||||
hardware_model: ""
|
||||
|
|
@ -42,6 +43,7 @@ spec:
|
|||
name: ""
|
||||
server_url: null
|
||||
memory: 0
|
||||
orbit_version: null
|
||||
os_version: ""
|
||||
osquery_version: ""
|
||||
pack_stats: null
|
||||
|
|
@ -54,6 +56,7 @@ spec:
|
|||
primary_mac: ""
|
||||
refetch_requested: false
|
||||
refetch_critical_queries_until: null
|
||||
scripts_enabled: null
|
||||
seen_time: "0001-01-01T00:00:00Z"
|
||||
software_updated_at: "0001-01-01T00:00:00Z"
|
||||
status: offline
|
||||
|
|
|
|||
|
|
@ -1644,6 +1644,32 @@ func (a *agent) diskEncryptionLinux() []map[string]string {
|
|||
}
|
||||
}
|
||||
|
||||
func (a *agent) orbitInfo() []map[string]string {
|
||||
version := "1.22.0"
|
||||
desktopVersion := version
|
||||
if a.disableFleetDesktop {
|
||||
desktopVersion = ""
|
||||
}
|
||||
deviceAuthToken := ""
|
||||
if a.deviceAuthToken != nil {
|
||||
deviceAuthToken = *a.deviceAuthToken
|
||||
}
|
||||
return []map[string]string{
|
||||
{
|
||||
"version": version,
|
||||
"device_auth_token": deviceAuthToken,
|
||||
"enrolled": "true",
|
||||
"last_recorded_error": "",
|
||||
"orbit_channel": "stable",
|
||||
"osqueryd_channel": "stable",
|
||||
"desktop_channel": "stable",
|
||||
"desktop_version": desktopVersion,
|
||||
"uptime": "10000",
|
||||
"scripts_enabled": "1",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *agent) runLiveQuery(query string) (results []map[string]string, status *fleet.OsqueryStatus, message *string, stats *fleet.Stats) {
|
||||
if a.liveQueryFailProb > 0.0 && rand.Float64() <= a.liveQueryFailProb {
|
||||
ss := fleet.OsqueryStatus(1)
|
||||
|
|
@ -1800,6 +1826,11 @@ func (a *agent) processQuery(name, query string) (
|
|||
// the caller knows it is handled, will not try to return lorem-ipsum-style
|
||||
// results.
|
||||
return true, nil, &statusNotOK, nil, nil
|
||||
case name == hostDetailQueryPrefix+"orbit_info":
|
||||
if a.orbitNodeKey == nil {
|
||||
return true, nil, &statusNotOK, nil, nil
|
||||
}
|
||||
return true, a.orbitInfo(), &statusOK, nil, nil
|
||||
default:
|
||||
// Look for results in the template file.
|
||||
if t := a.templates.Lookup(name); t == nil {
|
||||
|
|
|
|||
1
orbit/changes/17362-desktop-version-and-scripts-enabled
Normal file
1
orbit/changes/17362-desktop-version-and-scripts-enabled
Normal file
|
|
@ -0,0 +1 @@
|
|||
In orbit_info table, added desktop_version and scripts_enabled fields.
|
||||
|
|
@ -59,6 +59,13 @@ func setupRunners() {
|
|||
}
|
||||
|
||||
func main() {
|
||||
// Orbits uses --version to get the fleet-desktop version. Logs do not need to be set up when running this.
|
||||
if len(os.Args) > 1 && os.Args[1] == "--version" {
|
||||
// Must work with update.GetVersion
|
||||
fmt.Println("fleet-desktop", version)
|
||||
return
|
||||
}
|
||||
|
||||
setupLogs()
|
||||
setupStderr()
|
||||
|
||||
|
|
|
|||
|
|
@ -495,6 +495,7 @@ func main() {
|
|||
}
|
||||
|
||||
// Get current version of osquery
|
||||
log.Info().Msgf("orbit version: %s", build.Version)
|
||||
osquerydPath, err = updater.ExecutableLocalPath("osqueryd")
|
||||
if err != nil {
|
||||
log.Info().Err(err).Msg("Could not find local osqueryd executable")
|
||||
|
|
@ -803,7 +804,9 @@ func main() {
|
|||
windowsMDMBitlockerCommandFrequency = time.Hour
|
||||
)
|
||||
configFetcher := update.ApplyRenewEnrollmentProfileConfigFetcherMiddleware(orbitClient, renewEnrollmentProfileCommandFrequency, fleetURL)
|
||||
configFetcher = update.ApplyRunScriptsConfigFetcherMiddleware(configFetcher, c.Bool("enable-scripts"), orbitClient)
|
||||
configFetcher, scriptsEnabledFn := update.ApplyRunScriptsConfigFetcherMiddleware(
|
||||
configFetcher, c.Bool("enable-scripts"), orbitClient,
|
||||
)
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
|
|
@ -1079,6 +1082,20 @@ func main() {
|
|||
checkerClient.GetServerCapabilities().Copy(orbitClient.GetServerCapabilities())
|
||||
g.Add(capabilitiesChecker.actor())
|
||||
|
||||
var desktopVersion string
|
||||
if c.Bool("fleet-desktop") {
|
||||
runPath := desktopPath
|
||||
if runtime.GOOS == "darwin" {
|
||||
runPath = filepath.Join(desktopPath, "Contents", "MacOS", constant.DesktopAppExecName)
|
||||
}
|
||||
desktopVersion, err = update.GetVersion(runPath)
|
||||
if err == nil && desktopVersion != "" {
|
||||
log.Info().Msgf("Found fleet-desktop version: %s", desktopVersion)
|
||||
} else {
|
||||
desktopVersion = "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
registerExtensionRunner(
|
||||
&g,
|
||||
r.ExtensionSocketPath(),
|
||||
|
|
@ -1087,8 +1104,10 @@ func main() {
|
|||
c.String("orbit-channel"),
|
||||
c.String("osqueryd-channel"),
|
||||
c.String("desktop-channel"),
|
||||
desktopVersion,
|
||||
trw,
|
||||
startTime,
|
||||
scriptsEnabledFn,
|
||||
)),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,19 +19,26 @@ type Extension struct {
|
|||
orbitChannel string
|
||||
osquerydChannel string
|
||||
desktopChannel string
|
||||
dektopVersion string
|
||||
trw *token.ReadWriter
|
||||
scriptsEnabled func() bool
|
||||
}
|
||||
|
||||
var _ orbit_table.Extension = (*Extension)(nil)
|
||||
|
||||
func New(orbitClient *service.OrbitClient, orbitChannel, osquerydChannel, desktopChannel string, trw *token.ReadWriter, startTime time.Time) *Extension {
|
||||
func New(
|
||||
orbitClient *service.OrbitClient, orbitChannel, osquerydChannel, desktopChannel string, desktopVersion string, trw *token.ReadWriter,
|
||||
startTime time.Time, scriptsEnabled func() bool,
|
||||
) *Extension {
|
||||
return &Extension{
|
||||
startTime: startTime,
|
||||
orbitClient: orbitClient,
|
||||
orbitChannel: orbitChannel,
|
||||
osquerydChannel: osquerydChannel,
|
||||
desktopChannel: desktopChannel,
|
||||
dektopVersion: desktopVersion,
|
||||
trw: trw,
|
||||
scriptsEnabled: scriptsEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -50,7 +57,9 @@ func (o Extension) Columns() []table.ColumnDefinition {
|
|||
table.TextColumn("orbit_channel"),
|
||||
table.TextColumn("osqueryd_channel"),
|
||||
table.TextColumn("desktop_channel"),
|
||||
table.TextColumn("desktop_version"),
|
||||
table.BigIntColumn("uptime"),
|
||||
table.IntegerColumn("scripts_enabled"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -73,6 +82,17 @@ func (o Extension) GenerateFunc(_ context.Context, _ table.QueryContext) ([]map[
|
|||
}
|
||||
}
|
||||
|
||||
boolToInt := func(b bool) int64 {
|
||||
// Fast implementation according to https://0x0f.me/blog/golang-compiler-optimization/
|
||||
var i int64
|
||||
if b {
|
||||
i = 1
|
||||
} else {
|
||||
i = 0
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
return []map[string]string{{
|
||||
"version": v,
|
||||
"device_auth_token": token,
|
||||
|
|
@ -81,6 +101,8 @@ func (o Extension) GenerateFunc(_ context.Context, _ table.QueryContext) ([]map[
|
|||
"orbit_channel": o.orbitChannel,
|
||||
"osqueryd_channel": o.osquerydChannel,
|
||||
"desktop_channel": o.desktopChannel,
|
||||
"desktop_version": o.dektopVersion,
|
||||
"uptime": strconv.FormatInt(int64(time.Since(o.startTime).Seconds()), 10),
|
||||
"scripts_enabled": strconv.FormatInt(boolToInt(o.scriptsEnabled()), 10),
|
||||
}}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -314,7 +314,9 @@ type runScriptsConfigFetcher struct {
|
|||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func ApplyRunScriptsConfigFetcherMiddleware(fetcher OrbitConfigFetcher, scriptsEnabled bool, scriptsClient scripts.Client) OrbitConfigFetcher {
|
||||
func ApplyRunScriptsConfigFetcherMiddleware(
|
||||
fetcher OrbitConfigFetcher, scriptsEnabled bool, scriptsClient scripts.Client,
|
||||
) (OrbitConfigFetcher, func() bool) {
|
||||
scriptsFetcher := &runScriptsConfigFetcher{
|
||||
Fetcher: fetcher,
|
||||
ScriptsExecutionEnabled: scriptsEnabled,
|
||||
|
|
@ -323,7 +325,7 @@ func ApplyRunScriptsConfigFetcherMiddleware(fetcher OrbitConfigFetcher, scriptsE
|
|||
}
|
||||
// start the dynamic check for scripts enabled if required
|
||||
scriptsFetcher.runDynamicScriptsEnabledCheck()
|
||||
return scriptsFetcher
|
||||
return scriptsFetcher, scriptsFetcher.scriptsEnabled
|
||||
}
|
||||
|
||||
func (h *runScriptsConfigFetcher) runDynamicScriptsEnabledCheck() {
|
||||
|
|
@ -372,10 +374,7 @@ func (h *runScriptsConfigFetcher) GetConfig() (*fleet.OrbitConfig, error) {
|
|||
log.Debug().Msgf("received request to run scripts %v", cfg.Notifications.PendingScriptExecutionIDs)
|
||||
|
||||
runner := &scripts.Runner{
|
||||
// scripts are always enabled if the agent is started with the
|
||||
// --scripts-enabled flag. If it is not started with this flag, then
|
||||
// scripts are enabled only if the mdm profile says so.
|
||||
ScriptExecutionEnabled: h.ScriptsExecutionEnabled || h.dynamicScriptsEnabled.Load(),
|
||||
ScriptExecutionEnabled: h.scriptsEnabled(),
|
||||
Client: h.ScriptsClient,
|
||||
}
|
||||
fn := runner.Run
|
||||
|
|
@ -399,6 +398,13 @@ func (h *runScriptsConfigFetcher) GetConfig() (*fleet.OrbitConfig, error) {
|
|||
return cfg, err
|
||||
}
|
||||
|
||||
func (h *runScriptsConfigFetcher) scriptsEnabled() bool {
|
||||
// scripts are always enabled if the agent is started with the
|
||||
// --scripts-enabled flag. If it is not started with this flag, then
|
||||
// scripts are enabled only if the mdm profile says so.
|
||||
return h.ScriptsExecutionEnabled || h.dynamicScriptsEnabled.Load()
|
||||
}
|
||||
|
||||
type DiskEncryptionKeySetter interface {
|
||||
SetOrUpdateDiskEncryptionKey(diskEncryptionStatus fleet.OrbitHostDiskEncryptionKeyPayload) error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,6 +78,10 @@ func TestGetVersion(t *testing.T) {
|
|||
cmd: "#!/bin/bash\n/bin/echo orbit 4.5.6",
|
||||
version: "4.5.6",
|
||||
},
|
||||
"42.0.0": {
|
||||
cmd: "#!/bin/bash\n/bin/echo fleet-desktop 42.0.0",
|
||||
version: "42.0.0",
|
||||
},
|
||||
"5.10.2-26-gc396d07b4-dirty": {
|
||||
cmd: "#!/bin/bash\n/bin/echo osquery version 5.10.2-26-gc396d07b4-dirty",
|
||||
version: "5.10.2-26-gc396d07b4-dirty",
|
||||
|
|
|
|||
|
|
@ -630,7 +630,10 @@ SELECT
|
|||
host_id = h.id
|
||||
) AS additional,
|
||||
COALESCE(failing_policies.count, 0) AS failing_policies_count,
|
||||
COALESCE(failing_policies.count, 0) AS total_issues_count
|
||||
COALESCE(failing_policies.count, 0) AS total_issues_count,
|
||||
hoi.version AS orbit_version,
|
||||
hoi.desktop_version AS fleet_desktop_version,
|
||||
hoi.scripts_enabled AS scripts_enabled
|
||||
` + hostMDMSelect + `
|
||||
FROM
|
||||
hosts h
|
||||
|
|
@ -638,6 +641,7 @@ FROM
|
|||
LEFT JOIN host_seen_times hst ON (h.id = hst.host_id)
|
||||
LEFT JOIN host_updates hu ON (h.id = hu.host_id)
|
||||
LEFT JOIN host_disks hd ON hd.host_id = h.id
|
||||
LEFT JOIN host_orbit_info hoi ON hoi.host_id = h.id
|
||||
` + hostMDMJoin + `
|
||||
JOIN (
|
||||
SELECT
|
||||
|
|
@ -3693,12 +3697,14 @@ func (ds *Datastore) GetHostDiskEncryptionKey(ctx context.Context, hostID uint)
|
|||
return &key, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) SetOrUpdateHostOrbitInfo(ctx context.Context, hostID uint, version string) error {
|
||||
func (ds *Datastore) SetOrUpdateHostOrbitInfo(
|
||||
ctx context.Context, hostID uint, version string, desktopVersion sql.NullString, scriptsEnabled sql.NullBool,
|
||||
) error {
|
||||
return ds.updateOrInsert(
|
||||
ctx,
|
||||
`UPDATE host_orbit_info SET version = ? WHERE host_id = ?`,
|
||||
`INSERT INTO host_orbit_info (version, host_id) VALUES (?, ?)`,
|
||||
version, hostID,
|
||||
`UPDATE host_orbit_info SET version = ?, desktop_version = ?, scripts_enabled = ? WHERE host_id = ?`,
|
||||
`INSERT INTO host_orbit_info (version, desktop_version, scripts_enabled, host_id) VALUES (?, ?, ?, ?)`,
|
||||
version, desktopVersion, scriptsEnabled, hostID,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -211,16 +211,32 @@ func testUpdateHost(t *testing.T, ds *Datastore, updateHostFunc func(context.Con
|
|||
assert.Equal(t, policyUpdatedAt.UTC(), host.PolicyUpdatedAt)
|
||||
assert.NotNil(t, host.RefetchCriticalQueriesUntil)
|
||||
assert.True(t, time.Now().Before(*host.RefetchCriticalQueriesUntil))
|
||||
assert.Nil(t, host.OrbitVersion)
|
||||
assert.Nil(t, host.DesktopVersion)
|
||||
assert.Nil(t, host.ScriptsEnabled)
|
||||
|
||||
additionalJSON := json.RawMessage(`{"foobar": "bim"}`)
|
||||
err = ds.SaveHostAdditional(context.Background(), host.ID, &additionalJSON)
|
||||
require.NoError(t, err)
|
||||
// set host orbit info
|
||||
var (
|
||||
orbitVersion = "1.1.0"
|
||||
desktopVersion = "2.1.0"
|
||||
)
|
||||
err = ds.SetOrUpdateHostOrbitInfo(
|
||||
context.Background(), host.ID, orbitVersion, sql.NullString{String: desktopVersion, Valid: true},
|
||||
sql.NullBool{Bool: true, Valid: true},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
host, err = ds.Host(context.Background(), host.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, host)
|
||||
require.NotNil(t, host.Additional)
|
||||
assert.Equal(t, additionalJSON, *host.Additional)
|
||||
assert.Equal(t, orbitVersion, *host.OrbitVersion)
|
||||
assert.Equal(t, desktopVersion, *host.DesktopVersion)
|
||||
assert.True(t, *host.ScriptsEnabled)
|
||||
|
||||
err = updateHostFunc(context.Background(), host)
|
||||
require.NoError(t, err)
|
||||
|
|
@ -229,10 +245,18 @@ func testUpdateHost(t *testing.T, ds *Datastore, updateHostFunc func(context.Con
|
|||
err = updateHostFunc(context.Background(), host)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = ds.SetOrUpdateHostOrbitInfo(
|
||||
context.Background(), host.ID, orbitVersion, sql.NullString{Valid: false}, sql.NullBool{Valid: false},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
host, err = ds.Host(context.Background(), host.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, host)
|
||||
require.Nil(t, host.RefetchCriticalQueriesUntil)
|
||||
assert.Equal(t, orbitVersion, *host.OrbitVersion)
|
||||
assert.Nil(t, host.DesktopVersion)
|
||||
assert.Nil(t, host.ScriptsEnabled)
|
||||
|
||||
p, err := ds.NewPack(context.Background(), &fleet.Pack{
|
||||
Name: t.Name(),
|
||||
|
|
@ -6515,7 +6539,9 @@ func testHostsDeleteHosts(t *testing.T, ds *Datastore) {
|
|||
err = ds.SetOrUpdateHostDisksSpace(context.Background(), host.ID, 12, 25, 40.0)
|
||||
require.NoError(t, err)
|
||||
// set host orbit info
|
||||
err = ds.SetOrUpdateHostOrbitInfo(context.Background(), host.ID, "1.1.0")
|
||||
err = ds.SetOrUpdateHostOrbitInfo(
|
||||
context.Background(), host.ID, "1.1.0", sql.NullString{String: "2.1.0", Valid: true}, sql.NullBool{Bool: true, Valid: true},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
// set an encryption key
|
||||
err = ds.SetOrUpdateHostDiskEncryptionKey(context.Background(), host.ID, "TESTKEY", "", nil)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
package tables
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func init() {
|
||||
MigrationClient.AddMigration(Up_20240408085837, Down_20240408085837)
|
||||
}
|
||||
|
||||
func Up_20240408085837(tx *sql.Tx) error {
|
||||
_, err := tx.Exec(
|
||||
`ALTER TABLE host_orbit_info ADD COLUMN (
|
||||
desktop_version VARCHAR(50) DEFAULT NULL,
|
||||
scripts_enabled TINYINT(1) DEFAULT NULL
|
||||
)`,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add desktop_version and scripts_enabled to host_orbit_info: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Down_20240408085837(*sql.Tx) error {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package tables
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUp_20240408085837(t *testing.T) {
|
||||
db := applyUpToPrev(t)
|
||||
|
||||
// Insert data into orbit_info
|
||||
id := 1
|
||||
execNoErr(t, db, "INSERT INTO host_orbit_info (host_id, version) VALUES (?, ?)", id, "")
|
||||
|
||||
applyNext(t, db)
|
||||
|
||||
type orbitInfo struct {
|
||||
HostID int64 `db:"host_id"`
|
||||
Version string `db:"version"`
|
||||
DesktopVersion *string `db:"desktop_version"`
|
||||
ScriptsEnabled *bool `db:"scripts_enabled"`
|
||||
}
|
||||
|
||||
var results []orbitInfo
|
||||
err := db.SelectContext(context.Background(), &results, `SELECT * FROM host_orbit_info WHERE host_id = ?`, id)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, results, 1)
|
||||
assert.Nil(t, results[0].DesktopVersion)
|
||||
assert.Nil(t, results[0].ScriptsEnabled)
|
||||
|
||||
id = 2
|
||||
results = nil
|
||||
execNoErr(t, db, "INSERT INTO host_orbit_info (host_id, version) VALUES (?, ?)", id, "")
|
||||
err = db.SelectContext(context.Background(), &results, `SELECT * FROM host_orbit_info WHERE host_id = ?`, id)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, results, 1)
|
||||
assert.Nil(t, results[0].DesktopVersion)
|
||||
assert.Nil(t, results[0].ScriptsEnabled)
|
||||
|
||||
id = 3
|
||||
results = nil
|
||||
const desktopVersion = "1.0.0"
|
||||
const scriptsEnabled = true
|
||||
execNoErr(
|
||||
t, db, "INSERT INTO host_orbit_info (host_id, version, desktop_version, scripts_enabled) VALUES (?, ?, ?, ?)", id, "",
|
||||
desktopVersion,
|
||||
scriptsEnabled,
|
||||
)
|
||||
err = db.SelectContext(context.Background(), &results, `SELECT * FROM host_orbit_info WHERE host_id = ?`, id)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, results, 1)
|
||||
assert.Equal(t, desktopVersion, *results[0].DesktopVersion)
|
||||
assert.Equal(t, scriptsEnabled, *results[0].ScriptsEnabled)
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -2,6 +2,7 @@ package mysql
|
|||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
|
@ -97,7 +98,11 @@ func testStatisticsShouldSend(t *testing.T, ds *Datastore) {
|
|||
require.NoError(t, err)
|
||||
|
||||
// Create host_orbit_info record for test
|
||||
require.NoError(t, ds.SetOrUpdateHostOrbitInfo(ctx, h1.ID, "1.1.0"))
|
||||
require.NoError(
|
||||
t, ds.SetOrUpdateHostOrbitInfo(
|
||||
ctx, h1.ID, "1.1.0", sql.NullString{String: "1.1.0", Valid: true}, sql.NullBool{Bool: true, Valid: true},
|
||||
),
|
||||
)
|
||||
|
||||
// Create two new users for test
|
||||
u1, err := ds.NewUser(ctx, &fleet.User{
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package fleet
|
|||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
|
|
@ -836,7 +837,9 @@ type Datastore interface {
|
|||
GetHostMDMProfileRetryCountByCommandUUID(ctx context.Context, host *Host, cmdUUID string) (HostMDMProfileRetryCount, error)
|
||||
|
||||
// SetOrUpdateHostOrbitInfo inserts of updates the orbit info for a host
|
||||
SetOrUpdateHostOrbitInfo(ctx context.Context, hostID uint, version string) error
|
||||
SetOrUpdateHostOrbitInfo(
|
||||
ctx context.Context, hostID uint, version string, desktopVersion sql.NullString, scriptsEnabled sql.NullBool,
|
||||
) error
|
||||
|
||||
ReplaceHostDeviceMapping(ctx context.Context, id uint, mappings []*HostDeviceMapping, source string) error
|
||||
|
||||
|
|
|
|||
|
|
@ -545,6 +545,7 @@ const (
|
|||
RunScriptHostTimeoutErrMsg = "Fleet didn’t hear back from the host in under 5 minutes (timeout for live scripts). Fleet doesn’t know if the script ran because it didn’t receive the result. Please try again."
|
||||
RunScriptScriptsDisabledGloballyErrMsg = "Running scripts is disabled in organization settings."
|
||||
RunScriptDisabledErrMsg = "Scripts are disabled for this host. To run scripts, deploy the fleetd agent with scripts enabled."
|
||||
RunScriptsOrbitDisabledErrMsg = "Couldn't run script. To run a script, deploy the fleetd agent with --enable-scripts."
|
||||
RunScriptScriptTimeoutErrMsg = "Timeout. Fleet stopped the script after 5 minutes to protect host performance."
|
||||
RunScriptAsyncScriptEnqueuedErrMsg = "Script is running or will run when the host comes online."
|
||||
RunScripSavedMaxLenErrMsg = "Script is too large. It's limited to 500,000 characters (approximately 10,000 lines)."
|
||||
|
|
|
|||
|
|
@ -255,6 +255,9 @@ type Host struct {
|
|||
// Platform is the host's platform as defined by osquery's os_version.platform.
|
||||
Platform string `json:"platform" csv:"platform"`
|
||||
OsqueryVersion string `json:"osquery_version" db:"osquery_version" csv:"osquery_version"`
|
||||
OrbitVersion *string `json:"orbit_version" db:"orbit_version" csv:"orbit_version"`
|
||||
DesktopVersion *string `json:"fleet_desktop_version" db:"fleet_desktop_version" csv:"fleet_desktop_version"`
|
||||
ScriptsEnabled *bool `json:"scripts_enabled" db:"scripts_enabled" csv:"scripts_enabled"`
|
||||
OSVersion string `json:"os_version" db:"os_version" csv:"os_version"`
|
||||
Build string `json:"build" csv:"build"`
|
||||
PlatformLike string `json:"platform_like" db:"platform_like" csv:"platform_like"`
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ package mock
|
|||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"sync"
|
||||
|
|
@ -586,7 +587,7 @@ type GetHostMDMProfilesRetryCountsFunc func(ctx context.Context, host *fleet.Hos
|
|||
|
||||
type GetHostMDMProfileRetryCountByCommandUUIDFunc func(ctx context.Context, host *fleet.Host, cmdUUID string) (fleet.HostMDMProfileRetryCount, error)
|
||||
|
||||
type SetOrUpdateHostOrbitInfoFunc func(ctx context.Context, hostID uint, version string) error
|
||||
type SetOrUpdateHostOrbitInfoFunc func(ctx context.Context, hostID uint, version string, desktopVersion sql.NullString, scriptsEnabled sql.NullBool) error
|
||||
|
||||
type ReplaceHostDeviceMappingFunc func(ctx context.Context, id uint, mappings []*fleet.HostDeviceMapping, source string) error
|
||||
|
||||
|
|
@ -4210,11 +4211,11 @@ func (s *DataStore) GetHostMDMProfileRetryCountByCommandUUID(ctx context.Context
|
|||
return s.GetHostMDMProfileRetryCountByCommandUUIDFunc(ctx, host, cmdUUID)
|
||||
}
|
||||
|
||||
func (s *DataStore) SetOrUpdateHostOrbitInfo(ctx context.Context, hostID uint, version string) error {
|
||||
func (s *DataStore) SetOrUpdateHostOrbitInfo(ctx context.Context, hostID uint, version string, desktopVersion sql.NullString, scriptsEnabled sql.NullBool) error {
|
||||
s.mu.Lock()
|
||||
s.SetOrUpdateHostOrbitInfoFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.SetOrUpdateHostOrbitInfoFunc(ctx, hostID, version)
|
||||
return s.SetOrUpdateHostOrbitInfoFunc(ctx, hostID, version, desktopVersion, scriptsEnabled)
|
||||
}
|
||||
|
||||
func (s *DataStore) ReplaceHostDeviceMapping(ctx context.Context, id uint, mappings []*fleet.HostDeviceMapping, source string) error {
|
||||
|
|
|
|||
|
|
@ -7464,14 +7464,14 @@ func (s *integrationTestSuite) TestHostsReportDownload() {
|
|||
res.Body.Close()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, rows, len(hosts)+1) // all hosts + header row
|
||||
assert.Len(t, rows[0], 51) // total number of cols
|
||||
assert.Len(t, rows[0], 54) // total number of cols
|
||||
|
||||
const (
|
||||
idCol = 3
|
||||
issuesCol = 42
|
||||
gigsDiskCol = 39
|
||||
pctDiskCol = 40
|
||||
gigsTotalCol = 41
|
||||
issuesCol = 45
|
||||
gigsDiskCol = 42
|
||||
pctDiskCol = 43
|
||||
gigsTotalCol = 44
|
||||
)
|
||||
|
||||
// find the row for hosts[1], it should have issues=1 (1 failing policy) and the expected disk space
|
||||
|
|
@ -8507,6 +8507,10 @@ func createOrbitEnrolledHost(t *testing.T, os, suffix string, ds fleet.Datastore
|
|||
HardwareSerial: h.HardwareSerial,
|
||||
}, orbitKey, nil)
|
||||
require.NoError(t, err)
|
||||
err = ds.SetOrUpdateHostOrbitInfo(
|
||||
context.Background(), h.ID, "1.22.0", sql.NullString{String: "42", Valid: true}, sql.NullBool{Bool: true, Valid: true},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
h.OrbitNodeKey = &orbitKey
|
||||
return h
|
||||
}
|
||||
|
|
@ -9564,7 +9568,7 @@ func (s *integrationTestSuite) TestHostsReportWithPolicyResults() {
|
|||
res.Body.Close()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, rows1, len(hosts)+1) // all hosts + header row
|
||||
assert.Len(t, rows1[0], 51) // total number of cols
|
||||
assert.Len(t, rows1[0], 54) // total number of cols
|
||||
|
||||
var (
|
||||
idIdx int
|
||||
|
|
@ -9591,7 +9595,7 @@ func (s *integrationTestSuite) TestHostsReportWithPolicyResults() {
|
|||
res.Body.Close()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, rows2, len(hosts)+1) // all hosts + header row
|
||||
assert.Len(t, rows2[0], 51) // total number of cols
|
||||
assert.Len(t, rows2[0], 54) // total number of cols
|
||||
|
||||
// Check that all hosts have 0 issues and that they match the previous call to `/hosts/report`.
|
||||
for i := 1; i < len(hosts)+1; i++ {
|
||||
|
|
|
|||
|
|
@ -5106,6 +5106,27 @@ func (s *integrationEnterpriseTestSuite) TestRunHostScript() {
|
|||
require.True(t, scriptResultResp.HostTimeout)
|
||||
require.Contains(t, scriptResultResp.Message, fleet.RunScriptHostTimeoutErrMsg)
|
||||
|
||||
// Disable scripts on the host
|
||||
scriptsEnabled := false
|
||||
err = s.ds.SetOrUpdateHostOrbitInfo(
|
||||
context.Background(), host.ID, "1.22.0", sql.NullString{}, sql.NullBool{Bool: scriptsEnabled, Valid: true},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
s.DoJSON(
|
||||
"POST", "/api/latest/fleet/scripts/run", fleet.HostScriptRequestPayload{HostID: host.ID, ScriptContents: "echo"},
|
||||
http.StatusUnprocessableEntity, &runResp,
|
||||
)
|
||||
s.DoJSON(
|
||||
"POST", "/api/latest/fleet/scripts/run/sync", fleet.HostScriptRequestPayload{HostID: host.ID, ScriptContents: "echo"},
|
||||
http.StatusUnprocessableEntity, &runResp,
|
||||
)
|
||||
// Re-enable scripts on the host
|
||||
scriptsEnabled = true
|
||||
err = s.ds.SetOrUpdateHostOrbitInfo(
|
||||
context.Background(), host.ID, "1.22.0", sql.NullString{}, sql.NullBool{Bool: scriptsEnabled, Valid: true},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// make the host "offline"
|
||||
err = s.ds.MarkHostsSeen(context.Background(), []uint{host.ID}, time.Now().Add(-time.Hour))
|
||||
require.NoError(t, err)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package service
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
|
@ -1676,8 +1677,12 @@ func TestDetailQueries(t *testing.T) {
|
|||
require.Equal(t, "3.4.5", version)
|
||||
return nil
|
||||
}
|
||||
ds.SetOrUpdateHostOrbitInfoFunc = func(ctx context.Context, hostID uint, version string) error {
|
||||
ds.SetOrUpdateHostOrbitInfoFunc = func(
|
||||
ctx context.Context, hostID uint, version string, desktopVersion sql.NullString, scriptsEnabled sql.NullBool,
|
||||
) error {
|
||||
require.Equal(t, "42", version)
|
||||
require.Equal(t, sql.NullString{String: "1.2.3", Valid: true}, desktopVersion)
|
||||
require.Equal(t, sql.NullBool{Bool: true, Valid: true}, scriptsEnabled)
|
||||
return nil
|
||||
}
|
||||
ds.SetOrUpdateDeviceAuthTokenFunc = func(ctx context.Context, hostID uint, authToken string) error {
|
||||
|
|
@ -1851,7 +1856,9 @@ func TestDetailQueries(t *testing.T) {
|
|||
],
|
||||
"fleet_detail_query_orbit_info": [
|
||||
{
|
||||
"version": "42"
|
||||
"version": "42",
|
||||
"desktop_version": "1.2.3",
|
||||
"scripts_enabled": "1"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package osquery_utils
|
|||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
|
@ -607,8 +608,9 @@ var extraDetailQueries = map[string]DetailQuery{
|
|||
DirectIngestFunc: directIngestOSUnixLike,
|
||||
},
|
||||
"orbit_info": {
|
||||
Query: `SELECT version FROM orbit_info`,
|
||||
Query: `SELECT * FROM orbit_info`,
|
||||
DirectIngestFunc: directIngestOrbitInfo,
|
||||
Platforms: append(fleet.HostLinuxOSs, "darwin", "windows"),
|
||||
Discovery: discoveryTable("orbit_info"),
|
||||
},
|
||||
"disk_encryption_darwin": {
|
||||
|
|
@ -1054,7 +1056,15 @@ func directIngestOrbitInfo(ctx context.Context, logger log.Logger, host *fleet.H
|
|||
return ctxerr.Errorf(ctx, "directIngestOrbitInfo invalid number of rows: %d", len(rows))
|
||||
}
|
||||
version := rows[0]["version"]
|
||||
if err := ds.SetOrUpdateHostOrbitInfo(ctx, host.ID, version); err != nil {
|
||||
var desktopVersion sql.NullString
|
||||
desktopVersion.String, desktopVersion.Valid = rows[0]["desktop_version"]
|
||||
var scriptsEnabled sql.NullBool
|
||||
scriptsEnabledStr, ok := rows[0]["scripts_enabled"]
|
||||
if ok {
|
||||
scriptsEnabled.Bool = scriptsEnabledStr == "1"
|
||||
scriptsEnabled.Valid = true
|
||||
}
|
||||
if err := ds.SetOrUpdateHostOrbitInfo(ctx, host.ID, version, desktopVersion, scriptsEnabled); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "directIngestOrbitInfo update host orbit info")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -202,6 +202,13 @@ func (svc *Service) RunHostScript(ctx context.Context, request *fleet.HostScript
|
|||
return nil, fleet.NewUserMessageError(errors.New(fleet.RunScriptDisabledErrMsg), http.StatusUnprocessableEntity)
|
||||
}
|
||||
|
||||
// If scripts are disabled (according to the last detail query), we return an error.
|
||||
// host.ScriptsEnabled may be nil for older orbit versions.
|
||||
if host.ScriptsEnabled != nil && !*host.ScriptsEnabled {
|
||||
svc.authz.SkipAuthorization(ctx)
|
||||
return nil, fleet.NewUserMessageError(errors.New(fleet.RunScriptsOrbitDisabledErrMsg), http.StatusUnprocessableEntity)
|
||||
}
|
||||
|
||||
maxPending := maxPendingScripts
|
||||
|
||||
// authorize with the host's team and the script id provided, as both affect
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ Scripts in this directory aim to ease the testing of Orbit and the [TUF](https:/
|
|||
1. The script is executed on a macOS host.
|
||||
2. Fleet server also running on the same macOS host.
|
||||
3. All VMs (and the macOS host itself) are configured to resolve `host.docker.internal` to the macOS host IP (by modifying their `hosts` file).
|
||||
4. The hosts are running on the same GOARCH as the macOS host. If not, you can set the `GOARCH` environment variable to compile for the desired architecture. For example: `GOARCH=amd64`
|
||||
|
||||
> PS: We use `host.docker.internal` because the testing certificate `./tools/osquery/fleet.crt`
|
||||
> has such hostname (and `localhost`) defined as SANs.
|
||||
|
|
|
|||
Loading…
Reference in a new issue