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:
Victor Lyuboslavsky 2024-04-09 16:33:44 -05:00 committed by GitHub
parent 630b71875e
commit 3367b7e036
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 324 additions and 32 deletions

View file

@ -0,0 +1 @@
In GET fleet/hosts/:id response, added orbit_version, fleet_desktop_version, and scripts_enabled fields.

View file

@ -18,6 +18,9 @@
"uuid": "",
"platform": "",
"osquery_version": "",
"orbit_version": null,
"fleet_desktop_version": null,
"scripts_enabled": null,
"os_version": "",
"build": "",
"platform_like": "",

View file

@ -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

View file

@ -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": "",

View file

@ -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": "",

View file

@ -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

View file

@ -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 {

View file

@ -0,0 +1 @@
In orbit_info table, added desktop_version and scripts_enabled fields.

View file

@ -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()

View file

@ -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,
)),
)

View file

@ -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
}

View file

@ -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
}

View file

@ -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",

View file

@ -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,
)
}

View file

@ -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)

View file

@ -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
}

View file

@ -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

View file

@ -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{

View file

@ -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

View file

@ -545,6 +545,7 @@ const (
RunScriptHostTimeoutErrMsg = "Fleet didnt hear back from the host in under 5 minutes (timeout for live scripts). Fleet doesnt know if the script ran because it didnt 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)."

View file

@ -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"`

View file

@ -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 {

View file

@ -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++ {

View file

@ -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)

View file

@ -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"
}
]
}

View file

@ -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")
}

View file

@ -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

View file

@ -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.