fleet/ee/server/service/hosts.go
Victor Lyuboslavsky ba5f02f9ca
os_versions endpoint performance improvements (#34897)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #34500 and Resolves #33758

Video demo: https://www.youtube.com/watch?v=4HZlKG0G1B0

- Added a new aggregation table
`operating_system_version_vulnerabilities` for faster queries. The table
is currently used only for Linux vulnerabilities, but could be used for
other OS vulnerabilities.
- Added `max_vulnerabilities` parameter per [API
doc](https://github.com/fleetdm/fleet/pull/33533)
- Also added `max_vulnerabilities` parameter to `os_versions/{id}`
endpoint, but not making it public since that endpoint is still slow and
needs other API changes. bug #34974
- Removed `"kernels": []` from `os_versions` endpoint result

# 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`.

- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)

## Testing

- [x] Added/updated automated tests
- [x] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)

- [x] QA'd all new/changed functionality manually

## Database migrations

- [x] Checked schema for all modified table for columns that will
auto-update timestamps during migration.
- [x] Confirmed that updating the timestamps is acceptable, and will not
cause unwanted side effects.
- [x] Ensured the correct collation is explicitly set for character
columns (`COLLATE utf8mb4_unicode_ci`).

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added ability to limit the number of vulnerabilities displayed for
operating system versions via an optional parameter.
* Introduced vulnerability count tracking for operating system versions,
now visible in API responses and UI displays.
* Enhanced operating system vulnerability visualization with improved
count-based rendering.

* **Tests**
* Added comprehensive test coverage for vulnerability limiting behavior
across multiple operating system versions and architectures.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-11-03 13:07:44 -06:00

569 lines
21 KiB
Go

package service
import (
"context"
_ "embed"
"errors"
"fmt"
"net/http"
"time"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/go-kit/log/level"
"github.com/google/uuid"
)
func (svc *Service) GetHost(ctx context.Context, id uint, opts fleet.HostDetailOptions) (*fleet.HostDetail, error) {
// reuse GetHost, but include premium details
opts.IncludeCVEScores = true
opts.IncludePolicies = true
opts.IncludeCriticalVulnerabilitiesCount = true
return svc.Service.GetHost(ctx, id, opts)
}
func (svc *Service) HostByIdentifier(ctx context.Context, identifier string, opts fleet.HostDetailOptions) (*fleet.HostDetail, error) {
// reuse HostByIdentifier, but include premium options
opts.IncludeCVEScores = true
opts.IncludePolicies = true
return svc.Service.HostByIdentifier(ctx, identifier, opts)
}
func (svc *Service) OSVersions(ctx context.Context, teamID *uint, platform *string, name *string, version *string, opts fleet.ListOptions, _ bool,
maxVulnerabilities *int) (*fleet.OSVersions, int, *fleet.PaginationMetadata, error) {
// reuse OSVersions, but include premium options
return svc.Service.OSVersions(ctx, teamID, platform, name, version, opts, true, maxVulnerabilities)
}
func (svc *Service) OSVersion(ctx context.Context, osID uint, teamID *uint, _ bool, maxVulnerabilities *int) (*fleet.OSVersion, *time.Time, error) {
// reuse OSVersion, but include premium options
return svc.Service.OSVersion(ctx, osID, teamID, true, maxVulnerabilities)
}
func (svc *Service) LockHost(ctx context.Context, hostID uint, viewPIN bool) (unlockPIN string, err error) {
// First ensure the user has access to list hosts, then check the specific
// host once team_id is loaded.
if err := svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionList); err != nil {
return "", err
}
host, err := svc.ds.Host(ctx, hostID)
if err != nil {
return "", ctxerr.Wrap(ctx, err, "get host lite")
}
// Authorize again with team loaded now that we have the host's team_id.
// Authorize as "execute mdm_command", which is the correct access
// requirement and is what happens for macOS platforms.
if err := svc.authz.Authorize(ctx, fleet.MDMCommandAuthz{TeamID: host.TeamID}, fleet.ActionWrite); err != nil {
return "", err
}
// locking validations are based on the platform of the host
switch host.FleetPlatform() {
case "darwin", "ios", "ipados":
if host.MDM.EnrollmentStatus != nil && *host.MDM.EnrollmentStatus == "On (personal)" {
return "", &fleet.BadRequestError{
Message: fleet.CantLockPersonalHostsMessage,
}
}
if host.MDM.EnrollmentStatus != nil && *host.MDM.EnrollmentStatus == "On (manual)" &&
(host.FleetPlatform() == "ios" || host.FleetPlatform() == "ipados") {
return "", &fleet.BadRequestError{
Message: fleet.CantLockManualIOSIpadOSHostsMessage,
}
}
if err := svc.VerifyMDMAppleConfigured(ctx); err != nil {
if errors.Is(err, fleet.ErrMDMNotConfigured) {
err = fleet.NewInvalidArgumentError("host_id", fleet.AppleMDMNotConfiguredMessage).WithStatus(http.StatusBadRequest)
}
return "", ctxerr.Wrap(ctx, err, "check macOS MDM enabled")
}
// on macOS, the lock command requires the host to be MDM-enrolled in Fleet
connected, err := svc.ds.IsHostConnectedToFleetMDM(ctx, host)
if err != nil {
return "", ctxerr.Wrap(ctx, err, "checking if host is connected to Fleet")
}
if !connected {
return "", ctxerr.Wrap(
ctx, fleet.NewInvalidArgumentError("host_id", "Can't lock the host because it doesn't have MDM turned on."),
)
}
case "windows", "linux":
if host.FleetPlatform() == "windows" {
if err := svc.VerifyMDMWindowsConfigured(ctx); err != nil {
if errors.Is(err, fleet.ErrMDMNotConfigured) {
err = fleet.NewInvalidArgumentError("host_id", fleet.WindowsMDMNotConfiguredMessage).WithStatus(http.StatusBadRequest)
}
return "", ctxerr.Wrap(ctx, err, "check windows MDM enabled")
}
}
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)))
}
// if there's a lock, unlock or wipe action pending, do not accept the lock
// request.
lockWipe, err := svc.ds.GetHostLockWipeStatus(ctx, host)
if err != nil {
return "", ctxerr.Wrap(ctx, err, "get host lock/wipe status")
}
switch {
case lockWipe.IsPendingLock():
return "", ctxerr.Wrap(
ctx, fleet.NewInvalidArgumentError(
"host_id", "Host has pending lock request. Host cannot be locked again until lock is complete.",
),
)
case lockWipe.IsPendingUnlock():
return "", ctxerr.Wrap(
ctx, fleet.NewInvalidArgumentError(
"host_id", "Host has pending unlock request. Host cannot be locked again until unlock is complete.",
),
)
case lockWipe.IsPendingWipe():
return "", ctxerr.Wrap(
ctx,
fleet.NewInvalidArgumentError("host_id", "Host has pending wipe request. Cannot process lock requests once host is wiped."),
)
case lockWipe.IsWiped():
return "", ctxerr.Wrap(
ctx, fleet.NewInvalidArgumentError("host_id", "Host is wiped. Cannot process lock requests once host is wiped."),
)
case lockWipe.IsLocked():
return "", ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host is already locked.").WithStatus(http.StatusConflict))
}
// all good, go ahead with queuing the lock request.
return svc.enqueueLockHostRequest(ctx, host, lockWipe, viewPIN)
}
func (svc *Service) UnlockHost(ctx context.Context, hostID uint) (string, error) {
// First ensure the user has access to list hosts, then check the specific
// host once team_id is loaded.
if err := svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionList); err != nil {
return "", err
}
host, err := svc.ds.HostLite(ctx, hostID)
if err != nil {
return "", ctxerr.Wrap(ctx, err, "get host lite")
}
// Authorize again with team loaded now that we have the host's team_id.
// Authorize as "execute mdm_command", which is the correct access
// requirement.
if err := svc.authz.Authorize(ctx, fleet.MDMCommandAuthz{TeamID: host.TeamID}, fleet.ActionWrite); err != nil {
return "", err
}
// locking validations are based on the platform of the host
switch host.FleetPlatform() {
case "darwin", "ios", "ipados":
// all good, no need to check if MDM enrolled, will validate later that it
// is currently locked.
case "windows", "linux":
// on Windows and Linux, a script is used to unlock the host so scripts must
// be enabled on the host
if host.FleetPlatform() == "windows" {
if err := svc.VerifyMDMWindowsConfigured(ctx); err != nil {
if errors.Is(err, fleet.ErrMDMNotConfigured) {
err = fleet.NewInvalidArgumentError("host_id", fleet.WindowsMDMNotConfiguredMessage).WithStatus(http.StatusBadRequest)
}
return "", ctxerr.Wrap(ctx, err, "check windows MDM enabled")
}
}
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)))
}
lockWipe, err := svc.ds.GetHostLockWipeStatus(ctx, host)
if err != nil {
return "", ctxerr.Wrap(ctx, err, "get host lock/wipe status")
}
switch {
case lockWipe.IsPendingLock():
return "", ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host has pending lock request. Host cannot be unlocked until lock is complete."))
case lockWipe.IsPendingUnlock():
// MacOS machines are unlocked by typing the PIN into the machine. "Unlock" in this case
// should just return the PIN as many times as needed.
// Breaking here will fall through to call enqueueUnLockHostRequest which will return the PIN.
if host.FleetPlatform() == "darwin" {
break
}
return "", ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host has pending unlock request. The host will unlock when it comes online."))
case lockWipe.IsPendingWipe():
return "", ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host has pending wipe request. Cannot process unlock requests once host is wiped."))
case lockWipe.IsWiped():
return "", ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host is wiped. Cannot process unlock requests once host is wiped."))
case lockWipe.IsUnlocked():
return "", ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host is already unlocked.").WithStatus(http.StatusConflict))
}
// all good, go ahead with queuing the unlock request.
return svc.enqueueUnlockHostRequest(ctx, host, lockWipe)
}
func (svc *Service) WipeHost(ctx context.Context, hostID uint, metadata *fleet.MDMWipeMetadata) error {
// First ensure the user has access to list hosts, then check the specific
// host once team_id is loaded.
if err := svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionList); err != nil {
return err
}
host, err := svc.ds.Host(ctx, hostID)
if err != nil {
return ctxerr.Wrap(ctx, err, "get host lite")
}
// Authorize again with team loaded now that we have the host's team_id.
// Authorize as "execute mdm_command", which is the correct access
// requirement and is what happens for macOS platforms.
if err := svc.authz.Authorize(ctx, fleet.MDMCommandAuthz{TeamID: host.TeamID}, fleet.ActionWrite); err != nil {
return err
}
// wipe validations are based on the platform of the host, Windows and macOS
// require MDM to be enabled and the host to be MDM-enrolled in Fleet. Linux
// uses scripts, not MDM.
var requireMDM bool
switch host.FleetPlatform() {
case "darwin", "ios", "ipados":
if host.MDM.EnrollmentStatus != nil && *host.MDM.EnrollmentStatus == "On (personal)" {
return &fleet.BadRequestError{
Message: fleet.CantWipePersonalHostsMessage,
}
}
if err := svc.VerifyMDMAppleConfigured(ctx); err != nil {
if errors.Is(err, fleet.ErrMDMNotConfigured) {
err = fleet.NewInvalidArgumentError("host_id", fleet.AppleMDMNotConfiguredMessage).WithStatus(http.StatusBadRequest)
}
return ctxerr.Wrap(ctx, err, "check macOS MDM enabled")
}
requireMDM = true
case "windows":
if err := svc.VerifyMDMWindowsConfigured(ctx); err != nil {
if errors.Is(err, fleet.ErrMDMNotConfigured) {
err = fleet.NewInvalidArgumentError("host_id", fleet.WindowsMDMNotConfiguredMessage).WithStatus(http.StatusBadRequest)
}
return ctxerr.Wrap(ctx, err, "check windows MDM enabled")
}
requireMDM = true
case "linux":
// on linux, a script is used to wipe the host so scripts must be enabled on the host
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)))
}
if requireMDM {
// the wipe command requires the host to be MDM-enrolled in Fleet
connected, err := svc.ds.IsHostConnectedToFleetMDM(ctx, host)
if err != nil {
return ctxerr.Wrap(ctx, err, "checking if host is connected to Fleet")
}
if !connected {
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Can't wipe the host because it doesn't have MDM turned on."))
}
}
// validations based on host's actions status (pending lock, unlock, wipe)
lockWipe, err := svc.ds.GetHostLockWipeStatus(ctx, host)
if err != nil {
return ctxerr.Wrap(ctx, err, "get host lock/wipe status")
}
switch {
case lockWipe.IsPendingLock():
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host has pending lock request. Host cannot be wiped until lock is complete."))
case lockWipe.IsPendingUnlock():
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host has pending unlock request. Host cannot be wiped until unlock is complete."))
case lockWipe.IsPendingWipe():
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host has pending wipe request. The host will be wiped when it comes online."))
case lockWipe.IsLocked():
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host is locked. Host cannot be wiped until it is unlocked."))
case lockWipe.IsWiped():
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host is already wiped.").WithStatus(http.StatusConflict))
}
// all good, go ahead with queuing the wipe request.
return svc.enqueueWipeHostRequest(ctx, host, lockWipe, metadata)
}
func (svc *Service) enqueueLockHostRequest(ctx context.Context, host *fleet.Host, lockStatus *fleet.HostLockWipeStatus, viewPIN bool) (
unlockPIN string, err error,
) {
vc, ok := viewer.FromContext(ctx)
if !ok {
return "", fleet.ErrNoContext
}
activity := fleet.ActivityTypeLockedHost{
HostID: host.ID,
HostDisplayName: host.DisplayName(),
}
switch lockStatus.HostFleetPlatform {
case "darwin":
lockCommandUUID := uuid.NewString()
if unlockPIN, err = svc.mdmAppleCommander.DeviceLock(ctx, host, lockCommandUUID); err != nil {
return "", ctxerr.Wrap(ctx, err, "enqueuing lock request for macOS")
}
activity.ViewPIN = viewPIN
case "ios", "ipados":
appCfg, err := svc.ds.AppConfig(ctx)
if err != nil {
return "", ctxerr.Wrap(ctx, err, "get app config")
}
lockCommandUUID := uuid.NewString()
if err := svc.mdmAppleCommander.EnableLostMode(ctx, host, lockCommandUUID, appCfg.OrgInfo.OrgName); err != nil {
return "", ctxerr.Wrap(ctx, err, "enabling lost mode for iOS/iPadOS")
}
case "windows":
// TODO(mna): svc.RunHostScript should be refactored so that we can reuse the
// part starting with the validation of the script (just in case), the checks
// that we don't enqueue over the limit, etc. for any other important
// validation we may add over there and that we bypass here by enqueueing the
// script directly in the datastore layer.
if err := svc.ds.LockHostViaScript(ctx, &fleet.HostScriptRequestPayload{
HostID: host.ID,
ScriptContents: string(windowsLockScript),
UserID: &vc.User.ID,
SyncRequest: false,
}, host.FleetPlatform()); err != nil {
return "", err
}
case "linux":
// TODO(mna): svc.RunHostScript should be refactored so that we can reuse the
// part starting with the validation of the script (just in case), the checks
// that we don't enqueue over the limit, etc. for any other important
// validation we may add over there and that we bypass here by enqueueing the
// script directly in the datastore layer.
if err := svc.ds.LockHostViaScript(ctx, &fleet.HostScriptRequestPayload{
HostID: host.ID,
ScriptContents: string(linuxLockScript),
UserID: &vc.User.ID,
SyncRequest: false,
}, host.FleetPlatform()); err != nil {
return "", err
}
}
if err := svc.NewActivity(
ctx,
vc.User,
activity,
); err != nil {
return "", ctxerr.Wrap(ctx, err, "create activity for lock host request")
}
return unlockPIN, nil
}
func (svc *Service) enqueueUnlockHostRequest(ctx context.Context, host *fleet.Host, lockStatus *fleet.HostLockWipeStatus) (unlockPIN string, err error) {
vc, ok := viewer.FromContext(ctx)
if !ok {
return "", fleet.ErrNoContext
}
switch lockStatus.HostFleetPlatform {
case "darwin":
// Record the unlock request time if it was not already recorded.
// It should be always recorded, since the UnlockRequestedAt time is created after the lock command is acknowledged.
// This code is left here to catch potential issues.
if lockStatus.UnlockRequestedAt.IsZero() {
if err := svc.ds.UnlockHostManually(ctx, host.ID, host.FleetPlatform(), time.Now().UTC()); err != nil {
return "", err
}
}
unlockPIN = lockStatus.UnlockPIN
case "ios", "ipados":
err := svc.mdmAppleCommander.DisableLostMode(ctx, host, uuid.NewString())
if err != nil {
return "", ctxerr.Wrap(ctx, err, "disabling lost mode for iOS/iPadOS")
}
case "windows":
// TODO(mna): svc.RunHostScript should be refactored so that we can reuse the
// part starting with the validation of the script (just in case), the checks
// that we don't enqueue over the limit, etc. for any other important
// validation we may add over there and that we bypass here by enqueueing the
// script directly in the datastore layer.
if err := svc.ds.UnlockHostViaScript(ctx, &fleet.HostScriptRequestPayload{
HostID: host.ID,
ScriptContents: string(windowsUnlockScript),
UserID: &vc.User.ID,
SyncRequest: false,
}, host.FleetPlatform()); err != nil {
return "", err
}
case "linux":
// TODO(mna): svc.RunHostScript should be refactored so that we can reuse the
// part starting with the validation of the script (just in case), the checks
// that we don't enqueue over the limit, etc. for any other important
// validation we may add over there and that we bypass here by enqueueing the
// script directly in the datastore layer.
if err := svc.ds.UnlockHostViaScript(ctx, &fleet.HostScriptRequestPayload{
HostID: host.ID,
ScriptContents: string(linuxUnlockScript),
UserID: &vc.User.ID,
SyncRequest: false,
}, host.FleetPlatform()); err != nil {
return "", err
}
default:
return "", ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", fmt.Sprintf("Unsupported host platform: %s", host.Platform)))
}
if err := svc.NewActivity(
ctx,
vc.User,
fleet.ActivityTypeUnlockedHost{
HostID: host.ID,
HostDisplayName: host.DisplayName(),
HostPlatform: host.Platform,
},
); err != nil {
return "", ctxerr.Wrap(ctx, err, "create activity for unlock host request")
}
return unlockPIN, nil
}
func (svc *Service) enqueueWipeHostRequest(
ctx context.Context,
host *fleet.Host,
wipeStatus *fleet.HostLockWipeStatus,
metadata *fleet.MDMWipeMetadata,
) error {
vc, ok := viewer.FromContext(ctx)
if !ok {
return fleet.ErrNoContext
}
switch wipeStatus.HostFleetPlatform {
case "darwin", "ios", "ipados":
wipeCommandUUID := uuid.NewString()
if err := svc.mdmAppleCommander.EraseDevice(ctx, host, wipeCommandUUID); err != nil {
return ctxerr.Wrap(ctx, err, "enqueuing wipe request for darwin")
}
case "windows":
// default wipe type
wipeType := fleet.MDMWindowsWipeTypeDoWipeProtected
if metadata != nil && metadata.Windows != nil {
wipeType = metadata.Windows.WipeType
level.Debug(svc.logger).Log("msg", "Windows host wipe request", "wipe_type", wipeType.String())
}
wipeCmdUUID := uuid.NewString()
wipeCmd := &fleet.MDMWindowsCommand{
CommandUUID: wipeCmdUUID,
RawCommand: []byte(fmt.Sprintf(windowsWipeCommand, wipeCmdUUID, wipeType.String())),
TargetLocURI: fmt.Sprintf("./Device/Vendor/MSFT/RemoteWipe/%s", wipeType.String()),
}
if err := svc.ds.WipeHostViaWindowsMDM(ctx, host, wipeCmd); err != nil {
return ctxerr.Wrap(ctx, err, "enqueuing wipe request for windows")
}
case "linux":
// TODO(mna): svc.RunHostScript should be refactored so that we can reuse the
// part starting with the validation of the script (just in case), the checks
// that we don't enqueue over the limit, etc. for any other important
// validation we may add over there and that we bypass here by enqueueing the
// script directly in the datastore layer.
if err := svc.ds.WipeHostViaScript(ctx, &fleet.HostScriptRequestPayload{
HostID: host.ID,
ScriptContents: string(linuxWipeScript),
UserID: &vc.User.ID,
SyncRequest: false,
}, host.FleetPlatform()); err != nil {
return err
}
}
if err := svc.NewActivity(
ctx,
vc.User,
fleet.ActivityTypeWipedHost{
HostID: host.ID,
HostDisplayName: host.DisplayName(),
},
); err != nil {
return ctxerr.Wrap(ctx, err, "create activity for wipe host request")
}
return nil
}
var (
//go:embed embedded_scripts/windows_lock.ps1
windowsLockScript []byte
//go:embed embedded_scripts/windows_unlock.ps1
windowsUnlockScript []byte
//go:embed embedded_scripts/linux_lock.sh
linuxLockScript []byte
//go:embed embedded_scripts/linux_unlock.sh
linuxUnlockScript []byte
//go:embed embedded_scripts/linux_wipe.sh
linuxWipeScript []byte
windowsWipeCommand = `
<Exec>
<CmdID>%s</CmdID>
<Item>
<Target>
<LocURI>./Device/Vendor/MSFT/RemoteWipe/%s</LocURI>
</Target>
<Meta>
<Format xmlns="syncml:metinf">chr</Format>
<Type>text/plain</Type>
</Meta>
<Data></Data>
</Item>
</Exec>`
)