Added error messages when scripts are disabled. (#18174)
#17148 Added error messages to lock/unlock/wipe when scripts are disabled. # Checklist for submitter - [x] Added/updated tests - [x] Manual QA for all new/changed functionality
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -487,6 +488,21 @@ func TestMDMLockCommand(t *testing.T) {
|
||||
return h.MDMInfo, nil
|
||||
}
|
||||
|
||||
ds.GetHostOrbitInfoFunc = func(ctx context.Context, hostID uint) (*fleet.HostOrbitInfo, error) {
|
||||
hostIDMod := hostID % 3
|
||||
switch hostIDMod {
|
||||
case 0:
|
||||
return nil, ¬FoundError{}
|
||||
case 1:
|
||||
return &fleet.HostOrbitInfo{}, nil
|
||||
case 2:
|
||||
return &fleet.HostOrbitInfo{ScriptsEnabled: ptr.Bool(true)}, nil
|
||||
default:
|
||||
t.Errorf("unexpected hostIDMod %v", hostIDMod)
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
appCfgAllMDM, appCfgWinMDM, appCfgMacMDM, appCfgNoMDM := setupAppConigs()
|
||||
|
||||
successfulOutput := func(ident string) string {
|
||||
@@ -730,6 +746,21 @@ func TestMDMUnlockCommand(t *testing.T) {
|
||||
return h.MDMInfo, nil
|
||||
}
|
||||
|
||||
ds.GetHostOrbitInfoFunc = func(ctx context.Context, hostID uint) (*fleet.HostOrbitInfo, error) {
|
||||
hostIDMod := hostID % 3
|
||||
switch hostIDMod {
|
||||
case 0:
|
||||
return nil, ¬FoundError{}
|
||||
case 1:
|
||||
return &fleet.HostOrbitInfo{}, nil
|
||||
case 2:
|
||||
return &fleet.HostOrbitInfo{ScriptsEnabled: ptr.Bool(true)}, nil
|
||||
default:
|
||||
t.Errorf("unexpected hostIDMod %v", hostIDMod)
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
appCfgAllMDM, appCfgWinMDM, appCfgMacMDM, appCfgNoMDM := setupAppConigs()
|
||||
|
||||
successfulOutput := func(ident string) string {
|
||||
@@ -793,11 +824,6 @@ func TestMDMWipeCommand(t *testing.T) {
|
||||
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
|
||||
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual)")},
|
||||
}
|
||||
linuxEnrolled := &fleet.Host{
|
||||
ID: 3,
|
||||
UUID: "linux-enrolled",
|
||||
Platform: "linux",
|
||||
}
|
||||
winNotEnrolled := &fleet.Host{
|
||||
ID: 4,
|
||||
UUID: "win-not-enrolled",
|
||||
@@ -892,6 +918,23 @@ func TestMDMWipeCommand(t *testing.T) {
|
||||
MDMInfo: &fleet.HostMDM{Enrolled: true, Name: fleet.WellKnownMDMFleet},
|
||||
MDM: fleet.MDMHostData{Name: fleet.WellKnownMDMFleet, EnrollmentStatus: ptr.String("On (manual")},
|
||||
}
|
||||
linuxEnrolled := &fleet.Host{
|
||||
ID: 18,
|
||||
UUID: "linux-enrolled",
|
||||
Platform: "linux",
|
||||
}
|
||||
linuxEnrolled2 := &fleet.Host{
|
||||
ID: 19,
|
||||
UUID: "linux-enrolled",
|
||||
Platform: "linux",
|
||||
}
|
||||
linuxEnrolled3 := &fleet.Host{
|
||||
ID: 20,
|
||||
UUID: "linux-enrolled",
|
||||
Platform: "linux",
|
||||
}
|
||||
|
||||
linuxHostIDs := []uint{linuxEnrolled.ID, linuxEnrolled2.ID, linuxEnrolled3.ID}
|
||||
|
||||
hostByUUID := make(map[string]*fleet.Host)
|
||||
hostsByID := make(map[uint]*fleet.Host)
|
||||
@@ -899,6 +942,8 @@ func TestMDMWipeCommand(t *testing.T) {
|
||||
winEnrolled,
|
||||
macEnrolled,
|
||||
linuxEnrolled,
|
||||
linuxEnrolled2,
|
||||
linuxEnrolled3,
|
||||
macNotEnrolled,
|
||||
winNotEnrolled,
|
||||
macPending,
|
||||
@@ -1039,6 +1084,26 @@ func TestMDMWipeCommand(t *testing.T) {
|
||||
return h.MDMInfo, nil
|
||||
}
|
||||
|
||||
// This function should only run on linux
|
||||
ds.GetHostOrbitInfoFunc = func(ctx context.Context, hostID uint) (*fleet.HostOrbitInfo, error) {
|
||||
if !slices.Contains(linuxHostIDs, hostID) {
|
||||
t.Errorf("GetHostOrbitInfo should not be called for non-linux host %v", hostID)
|
||||
return nil, nil
|
||||
}
|
||||
hostIDMod := hostID % 3
|
||||
switch hostIDMod {
|
||||
case 0:
|
||||
return nil, ¬FoundError{}
|
||||
case 1:
|
||||
return &fleet.HostOrbitInfo{}, nil
|
||||
case 2:
|
||||
return &fleet.HostOrbitInfo{ScriptsEnabled: ptr.Bool(true)}, nil
|
||||
default:
|
||||
t.Errorf("unexpected hostIDMod %v", hostIDMod)
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
appCfgAllMDM, appCfgWinMDM, appCfgMacMDM, appCfgNoMDM := setupAppConigs()
|
||||
appCfgScriptsDisabled := &fleet.AppConfig{ServerSettings: fleet.ServerSettings{ScriptsDisabled: true}}
|
||||
|
||||
@@ -1055,6 +1120,8 @@ func TestMDMWipeCommand(t *testing.T) {
|
||||
{appCfgAllMDM, "valid windows", []string{"--host", winEnrolled.UUID}, ""},
|
||||
{appCfgAllMDM, "valid macos", []string{"--host", macEnrolled.UUID}, ""},
|
||||
{appCfgNoMDM, "valid linux", []string{"--host", linuxEnrolled.UUID}, ""},
|
||||
{appCfgNoMDM, "valid linux 2", []string{"--host", linuxEnrolled2.UUID}, ""},
|
||||
{appCfgNoMDM, "valid linux 3", []string{"--host", linuxEnrolled3.UUID}, ""},
|
||||
{appCfgNoMDM, "valid windows but no mdm", []string{"--host", winEnrolled.UUID}, `Windows MDM isn't turned on.`},
|
||||
{appCfgMacMDM, "valid macos but not enrolled", []string{"--host", macNotEnrolled.UUID}, `Can't wipe the host because it doesn't have MDM turned on.`},
|
||||
{appCfgWinMDM, "valid windows but not enrolled", []string{"--host", winNotEnrolled.UUID}, `Can't wipe the host because it doesn't have MDM turned on.`},
|
||||
|
||||
@@ -96,6 +96,20 @@ func (svc *Service) LockHost(ctx context.Context, hostID uint) error {
|
||||
if appCfg.ServerSettings.ScriptsDisabled {
|
||||
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Can't lock host because running scripts is disabled in organization settings."))
|
||||
}
|
||||
hostOrbitInfo, err := svc.ds.GetHostOrbitInfo(ctx, host.ID)
|
||||
switch {
|
||||
case err != nil:
|
||||
// If not found, then do nothing. We do not know if this host has scripts enabled or not
|
||||
if !fleet.IsNotFound(err) {
|
||||
return ctxerr.Wrap(ctx, err, "get host orbit info")
|
||||
}
|
||||
case hostOrbitInfo.ScriptsEnabled != nil && !*hostOrbitInfo.ScriptsEnabled:
|
||||
return ctxerr.Wrap(
|
||||
ctx, fleet.NewInvalidArgumentError(
|
||||
"host_id", "Couldn't lock host. To lock, deploy the fleetd agent with --enable-scripts and refetch host vitals.",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
default:
|
||||
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", fmt.Sprintf("Unsupported host platform: %s", host.Platform)))
|
||||
@@ -166,6 +180,20 @@ func (svc *Service) UnlockHost(ctx context.Context, hostID uint) (string, error)
|
||||
if appCfg.ServerSettings.ScriptsDisabled {
|
||||
return "", ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Can't unlock host because running scripts is disabled in organization settings."))
|
||||
}
|
||||
hostOrbitInfo, err := svc.ds.GetHostOrbitInfo(ctx, host.ID)
|
||||
switch {
|
||||
case err != nil:
|
||||
// If not found, then do nothing. We do not know if this host has scripts enabled or not
|
||||
if !fleet.IsNotFound(err) {
|
||||
return "", ctxerr.Wrap(ctx, err, "get host orbit info")
|
||||
}
|
||||
case hostOrbitInfo.ScriptsEnabled != nil && !*hostOrbitInfo.ScriptsEnabled:
|
||||
return "", ctxerr.Wrap(
|
||||
ctx, fleet.NewInvalidArgumentError(
|
||||
"host_id", "Couldn't unlock host. To unlock, deploy the fleetd agent with --enable-scripts and refetch host vitals.",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
default:
|
||||
return "", ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", fmt.Sprintf("Unsupported host platform: %s", host.Platform)))
|
||||
@@ -248,6 +276,20 @@ func (svc *Service) WipeHost(ctx context.Context, hostID uint) error {
|
||||
if appCfg.ServerSettings.ScriptsDisabled {
|
||||
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Can't wipe host because running scripts is disabled in organization settings."))
|
||||
}
|
||||
hostOrbitInfo, err := svc.ds.GetHostOrbitInfo(ctx, host.ID)
|
||||
switch {
|
||||
case err != nil:
|
||||
// If not found, then do nothing. We do not know if this host has scripts enabled or not
|
||||
if !fleet.IsNotFound(err) {
|
||||
return ctxerr.Wrap(ctx, err, "get host orbit info")
|
||||
}
|
||||
case hostOrbitInfo.ScriptsEnabled != nil && !*hostOrbitInfo.ScriptsEnabled:
|
||||
return ctxerr.Wrap(
|
||||
ctx, fleet.NewInvalidArgumentError(
|
||||
"host_id", "Couldn't wipe host. To wipe, deploy the fleetd agent with --enable-scripts and refetch host vitals.",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
default:
|
||||
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", fmt.Sprintf("Unsupported host platform: %s", host.Platform)))
|
||||
|
||||
@@ -3708,6 +3708,25 @@ func (ds *Datastore) SetOrUpdateHostOrbitInfo(
|
||||
)
|
||||
}
|
||||
|
||||
func (ds *Datastore) GetHostOrbitInfo(ctx context.Context, hostID uint) (*fleet.HostOrbitInfo, error) {
|
||||
var orbit fleet.HostOrbitInfo
|
||||
err := sqlx.GetContext(
|
||||
ctx, ds.reader(ctx), &orbit, `
|
||||
SELECT
|
||||
scripts_enabled
|
||||
FROM
|
||||
host_orbit_info
|
||||
WHERE host_id = ?`, hostID,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, ctxerr.Wrap(ctx, notFound("HostOrbitInfo").WithID(hostID))
|
||||
}
|
||||
return nil, ctxerr.Wrapf(ctx, err, "select host_orbit_info for host_id %d", hostID)
|
||||
}
|
||||
return &orbit, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) getOrInsertMDMSolution(ctx context.Context, serverURL string, mdmName string) (mdmID uint, err error) {
|
||||
readStmt := ¶meterizedStmt{
|
||||
Statement: `SELECT id FROM mobile_device_management_solutions WHERE name = ? AND server_url = ?`,
|
||||
|
||||
@@ -163,6 +163,7 @@ func TestHosts(t *testing.T) {
|
||||
{"ListHostsWithPagination", testListHostsWithPagination},
|
||||
{"LastRestarted", testLastRestarted},
|
||||
{"HostHealth", testHostHealth},
|
||||
{"GetHostOrbitInfo", testGetHostOrbitInfo},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
@@ -8756,3 +8757,41 @@ func testHostHealth(t *testing.T, ds *Datastore) {
|
||||
require.Empty(t, hh.VulnerableSoftware)
|
||||
require.Equal(t, h.TeamID, hh.TeamID)
|
||||
}
|
||||
|
||||
func testGetHostOrbitInfo(t *testing.T, ds *Datastore) {
|
||||
host, err := ds.NewHost(
|
||||
context.Background(), &fleet.Host{
|
||||
DetailUpdatedAt: time.Now(),
|
||||
LabelUpdatedAt: time.Now(),
|
||||
PolicyUpdatedAt: time.Now(),
|
||||
SeenTime: time.Now(),
|
||||
NodeKey: ptr.String("1"),
|
||||
UUID: "1",
|
||||
Hostname: "foo.local",
|
||||
PrimaryIP: "192.168.1.1",
|
||||
PrimaryMac: "30-65-EC-6F-C4-58",
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, host)
|
||||
|
||||
_, err = ds.GetHostOrbitInfo(context.Background(), host.ID)
|
||||
require.True(t, fleet.IsNotFound(err))
|
||||
|
||||
orbitVersion := "1.1.0"
|
||||
err = ds.SetOrUpdateHostOrbitInfo(
|
||||
context.Background(), host.ID, orbitVersion, sql.NullString{Valid: false}, sql.NullBool{Valid: false},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
hostOrbitInfo, err := ds.GetHostOrbitInfo(context.Background(), host.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, hostOrbitInfo.ScriptsEnabled)
|
||||
|
||||
err = ds.SetOrUpdateHostOrbitInfo(
|
||||
context.Background(), host.ID, orbitVersion, sql.NullString{Valid: false}, sql.NullBool{Bool: true, Valid: true},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
hostOrbitInfo, err = ds.GetHostOrbitInfo(context.Background(), host.ID)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, *hostOrbitInfo.ScriptsEnabled)
|
||||
}
|
||||
|
||||
@@ -841,6 +841,8 @@ type Datastore interface {
|
||||
ctx context.Context, hostID uint, version string, desktopVersion sql.NullString, scriptsEnabled sql.NullBool,
|
||||
) error
|
||||
|
||||
GetHostOrbitInfo(ctx context.Context, hostID uint) (*HostOrbitInfo, error)
|
||||
|
||||
ReplaceHostDeviceMapping(ctx context.Context, id uint, mappings []*HostDeviceMapping, source string) error
|
||||
|
||||
// ReplaceHostBatteries creates or updates the battery mappings of a host.
|
||||
|
||||
@@ -359,6 +359,11 @@ type Host struct {
|
||||
Policies *[]*HostPolicy `json:"policies,omitempty" csv:"-"`
|
||||
}
|
||||
|
||||
// HostOrbitInfo maps to the host_orbit_info table in the database, which maps to the orbit_info agent table.
|
||||
type HostOrbitInfo struct {
|
||||
ScriptsEnabled *bool `json:"scripts_enabled" db:"scripts_enabled"`
|
||||
}
|
||||
|
||||
// HostHealth contains a subset of Host data that indicates how healthy a Host is. For fields with
|
||||
// the same name, see the comments/docs for the Host field above.
|
||||
type HostHealth struct {
|
||||
|
||||
@@ -589,6 +589,8 @@ type GetHostMDMProfileRetryCountByCommandUUIDFunc func(ctx context.Context, host
|
||||
|
||||
type SetOrUpdateHostOrbitInfoFunc func(ctx context.Context, hostID uint, version string, desktopVersion sql.NullString, scriptsEnabled sql.NullBool) error
|
||||
|
||||
type GetHostOrbitInfoFunc func(ctx context.Context, hostID uint) (*fleet.HostOrbitInfo, error)
|
||||
|
||||
type ReplaceHostDeviceMappingFunc func(ctx context.Context, id uint, mappings []*fleet.HostDeviceMapping, source string) error
|
||||
|
||||
type ReplaceHostBatteriesFunc func(ctx context.Context, id uint, mappings []*fleet.HostBattery) error
|
||||
@@ -1755,6 +1757,9 @@ type DataStore struct {
|
||||
SetOrUpdateHostOrbitInfoFunc SetOrUpdateHostOrbitInfoFunc
|
||||
SetOrUpdateHostOrbitInfoFuncInvoked bool
|
||||
|
||||
GetHostOrbitInfoFunc GetHostOrbitInfoFunc
|
||||
GetHostOrbitInfoFuncInvoked bool
|
||||
|
||||
ReplaceHostDeviceMappingFunc ReplaceHostDeviceMappingFunc
|
||||
ReplaceHostDeviceMappingFuncInvoked bool
|
||||
|
||||
@@ -4218,6 +4223,13 @@ func (s *DataStore) SetOrUpdateHostOrbitInfo(ctx context.Context, hostID uint, v
|
||||
return s.SetOrUpdateHostOrbitInfoFunc(ctx, hostID, version, desktopVersion, scriptsEnabled)
|
||||
}
|
||||
|
||||
func (s *DataStore) GetHostOrbitInfo(ctx context.Context, hostID uint) (*fleet.HostOrbitInfo, error) {
|
||||
s.mu.Lock()
|
||||
s.GetHostOrbitInfoFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.GetHostOrbitInfoFunc(ctx, hostID)
|
||||
}
|
||||
|
||||
func (s *DataStore) ReplaceHostDeviceMapping(ctx context.Context, id uint, mappings []*fleet.HostDeviceMapping, source string) error {
|
||||
s.mu.Lock()
|
||||
s.ReplaceHostDeviceMappingFuncInvoked = true
|
||||
|
||||
@@ -7244,6 +7244,28 @@ func (s *integrationEnterpriseTestSuite) TestLockUnlockWipeWindowsLinux() {
|
||||
errMsg = extractServerErrorText(res.Body)
|
||||
require.Contains(t, errMsg, "Windows MDM isn't turned on.")
|
||||
|
||||
// Disable scripts on Linux host
|
||||
err := s.ds.SetOrUpdateHostOrbitInfo(
|
||||
context.Background(), linuxHost.ID, "1.22.0", sql.NullString{}, sql.NullBool{Bool: false, Valid: true},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
// try to lock/unlock/wipe the Linux host. Fails because scripts are not enabled.
|
||||
res = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/lock", linuxHost.ID), nil, http.StatusUnprocessableEntity)
|
||||
errMsg = extractServerErrorText(res.Body)
|
||||
require.Contains(t, errMsg, "Couldn't lock host. To lock, deploy the fleetd agent with --enable-scripts")
|
||||
res = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/unlock", linuxHost.ID), nil, http.StatusUnprocessableEntity)
|
||||
errMsg = extractServerErrorText(res.Body)
|
||||
require.Contains(t, errMsg, "Couldn't unlock host. To unlock, deploy the fleetd agent with --enable-scripts")
|
||||
res = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/wipe", linuxHost.ID), nil, http.StatusUnprocessableEntity)
|
||||
errMsg = extractServerErrorText(res.Body)
|
||||
require.Contains(t, errMsg, "Couldn't wipe host. To wipe, deploy the fleetd agent with --enable-scripts")
|
||||
|
||||
// Enable scripts on Linux host
|
||||
err = s.ds.SetOrUpdateHostOrbitInfo(
|
||||
context.Background(), linuxHost.ID, "1.22.0", sql.NullString{}, sql.NullBool{Bool: true, Valid: true},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// try to lock/unlock/wipe the Linux host succeeds, no MDM constraints
|
||||
s.Do("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/lock", linuxHost.ID), nil, http.StatusNoContent)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user