fleet/server/worker/software_worker.go
Jahziel Villasana-Espinoza ff0ba413b7
Android app self service: backend support (#34711)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #34389

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

- [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] Ensured the correct collation is explicitly set for character
columns (`COLLATE utf8mb4_unicode_ci`).
2025-11-13 18:10:24 -05:00

185 lines
6.1 KiB
Go

package worker
import (
"context"
"encoding/json"
"fmt"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/mdm/android"
kitlog "github.com/go-kit/log"
"github.com/go-kit/log/level"
"google.golang.org/api/androidmanagement/v1"
)
const softwareWorkerJobName = "software_worker"
type SoftwareWorkerTask string
type SoftwareWorker struct {
Datastore fleet.Datastore
AndroidModule android.Service
Log kitlog.Logger
}
func (v *SoftwareWorker) Name() string {
return softwareWorkerJobName
}
const makeAndroidAppsAvailableForHostTask SoftwareWorkerTask = "make_android_apps_available_for_host"
const makeAndroidAppAvailableTask SoftwareWorkerTask = "make_android_app_available"
type softwareWorkerArgs struct {
Task SoftwareWorkerTask `json:"task"`
HostUUID string `json:"host_uuid"`
ApplicationID string `json:"application_id"`
EnterpriseName string `json:"enterprise_name"`
AppTeamID uint `json:"app_team_id"`
HostID uint `json:"host_id"`
PolicyID string `json:"policy_id"`
}
func (v *SoftwareWorker) Run(ctx context.Context, argsJSON json.RawMessage) error {
var args softwareWorkerArgs
if err := json.Unmarshal(argsJSON, &args); err != nil {
return ctxerr.Wrap(ctx, err, "unmarshal args")
}
switch args.Task {
case makeAndroidAppsAvailableForHostTask:
return ctxerr.Wrapf(
ctx,
v.makeAndroidAppsAvailableForHost(ctx, args.HostUUID, args.HostID, args.EnterpriseName, args.PolicyID),
"running %s task",
makeAndroidAppsAvailableForHostTask,
)
case makeAndroidAppAvailableTask:
return ctxerr.Wrapf(
ctx,
v.makeAndroidAppAvailable(ctx, args.ApplicationID, args.AppTeamID, args.EnterpriseName),
"running %s task",
makeAndroidAppAvailableTask,
)
default:
return ctxerr.Errorf(ctx, "unknown task: %v", args.Task)
}
}
func (v *SoftwareWorker) makeAndroidAppAvailable(ctx context.Context, applicationID string, appTeamID uint, enterpriseName string) error {
hosts, err := v.Datastore.GetIncludedHostUUIDMapForAppStoreApp(ctx, appTeamID)
if err != nil {
return ctxerr.Wrap(ctx, err, "add app store app: getting android hosts in scope")
}
// Update Android MDM policy to include the app in self service
err = v.AndroidModule.AddAppToAndroidPolicy(ctx, enterpriseName, []string{applicationID}, hosts)
if err != nil {
return ctxerr.Wrap(ctx, err, "add app store app: add app to android policy")
}
return nil
}
func QueueMakeAndroidAppAvailableJob(ctx context.Context, ds fleet.Datastore, logger kitlog.Logger, applicationID string, appTeamID uint, enterpriseName string) error {
args := &softwareWorkerArgs{
Task: makeAndroidAppAvailableTask,
ApplicationID: applicationID,
AppTeamID: appTeamID,
EnterpriseName: enterpriseName,
}
job, err := QueueJob(ctx, ds, softwareWorkerJobName, args)
if err != nil {
return ctxerr.Wrap(ctx, err, "queueing job")
}
level.Debug(logger).Log("job_id", job.ID, "job_name", softwareWorkerJobName, "task", makeAndroidAppAvailableTask)
return nil
}
func (v *SoftwareWorker) makeAndroidAppsAvailableForHost(ctx context.Context, hostUUID string, hostID uint, enterpriseName, policyID string) error {
if policyID == "1" {
var policy androidmanagement.Policy
policy.StatusReportingSettings = &androidmanagement.StatusReportingSettings{
DeviceSettingsEnabled: true,
MemoryInfoEnabled: true,
NetworkInfoEnabled: true,
DisplayInfoEnabled: true,
PowerManagementEventsEnabled: true,
HardwareStatusEnabled: true,
SystemPropertiesEnabled: true,
SoftwareInfoEnabled: true,
CommonCriteriaModeEnabled: true,
ApplicationReportsEnabled: true,
ApplicationReportingSettings: nil, // only option is "includeRemovedApps", which I opted not to enable (we can diff apps to see removals)
}
policyName := fmt.Sprintf("%s/policies/%s", enterpriseName, hostUUID)
_, err := v.AndroidModule.PatchPolicy(ctx, hostUUID, policyName, &policy, nil)
if err != nil {
return err
}
device := &androidmanagement.Device{
PolicyName: policyName,
// State must be specified when updating a device, otherwise it fails with
// "Illegal state transition from ACTIVE to DEVICE_STATE_UNSPECIFIED"
//
// > Note that when calling enterprises.devices.patch, ACTIVE and
// > DISABLED are the only allowable values.
// TODO(ap): should we send whatever the previous state was? If it was DISABLED,
// we probably don't want to re-enable it by accident. Those are the only
// 2 valid states when patching a device.
State: "ACTIVE",
}
androidHost, err := v.Datastore.AndroidHostLiteByHostUUID(ctx, hostUUID)
if err != nil {
return ctxerr.Wrapf(ctx, err, "get android host by host UUID %s", hostUUID)
}
deviceName := fmt.Sprintf("%s/devices/%s", enterpriseName, androidHost.DeviceID)
_, err = v.AndroidModule.PatchDevice(ctx, hostUUID, deviceName, device)
if err != nil {
return err
}
}
appIDs, err := v.Datastore.GetAndroidAppsInScopeForHost(ctx, hostID)
if err != nil {
return ctxerr.Wrap(ctx, err, "get android apps in scope for host")
}
if len(appIDs) == 0 {
return nil
}
err = v.AndroidModule.AddAppToAndroidPolicy(ctx, enterpriseName, appIDs, map[string]string{hostUUID: hostUUID})
if err != nil {
return ctxerr.Wrap(ctx, err, "add app store app: add app to android policy")
}
return nil
}
func QueueMakeAndroidAppsAvailableForHostJob(ctx context.Context, ds fleet.Datastore, logger kitlog.Logger, hostUUID string, hostID uint, enterpriseName, policyID string) error {
args := &softwareWorkerArgs{
Task: makeAndroidAppsAvailableForHostTask,
HostUUID: hostUUID,
HostID: hostID,
EnterpriseName: enterpriseName,
PolicyID: policyID,
}
job, err := QueueJob(ctx, ds, softwareWorkerJobName, args)
if err != nil {
return ctxerr.Wrap(ctx, err, "queueing job")
}
level.Debug(logger).Log("job_id", job.ID, "job_name", softwareWorkerJobName, "task", makeAndroidAppsAvailableForHostTask)
return nil
}