mirror of
https://github.com/fleetdm/fleet
synced 2026-05-17 22:18:39 +00:00
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #35434 Feature is largely behind feature flag `FLEET_DEV_ANDROID_AGENT_PACKAGE` Set it like: `export FLEET_DEV_ANDROID_AGENT_PACKAGE=com.fleetdm.agent.private.victor` Rough set up: 1. Change the applicationId of your Android app in `build.gradle.kts`: ```kt defaultConfig { applicationId = "com.fleetdm.agent.private.you" ``` 2. Build a release version of your app (use dummy signing key). Build -> Generate Signed App Bundle or APK ... 3. Get the super secret Google Play URL like: `go run tools/android/android.go --command enterprises.webTokens.create --enterprise_id 'XXXX'` 4. Upload your signed app. 5. Wait ~10 minutes 6. Enroll your Android device. 7. The agent should start installing pretty soon. Check your Google Play in Work profile. Mine was pending for a while the last time I tried it and I restarted the device before it actually started installing. @ksykulev you can use this Android service method for "notification": `AddFleetAgentToAndroidPolicy(ctx context.Context, enterpriseName string, hostConfigs map[string]AgentManagedConfiguration) error` You'll need to update `AgentManagedConfiguration` struct to define what to send down to the device. It includes the enroll secret, so I think we need to send it down every time just to be safe. # Checklist for submitter - Changes file will be updated when full feature is done. ## Testing - [x] QA'd all new/changed functionality manually
215 lines
7.1 KiB
Go
215 lines
7.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" {
|
|
// Get the host once for both enroll secret and device patching
|
|
androidHost, err := v.Datastore.AndroidHostLiteByHostUUID(ctx, hostUUID)
|
|
if err != nil {
|
|
return ctxerr.Wrapf(ctx, err, "get android host by host UUID %s", hostUUID)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// Get enroll secrets for the host's team (nil means global/no team)
|
|
enrollSecrets, err := v.Datastore.GetEnrollSecrets(ctx, androidHost.Host.TeamID)
|
|
if err != nil {
|
|
return ctxerr.Wrapf(ctx, err, "get enroll secrets for team %v", androidHost.Host.TeamID)
|
|
}
|
|
if len(enrollSecrets) == 0 {
|
|
return ctxerr.Errorf(ctx, "no enroll secrets found for team %v", androidHost.Host.TeamID)
|
|
}
|
|
// Use the first enroll secret
|
|
enrollSecret := enrollSecrets[0].Secret
|
|
|
|
appConfig, err := v.Datastore.AppConfig(ctx)
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "get app config")
|
|
}
|
|
|
|
err = v.AndroidModule.AddFleetAgentToAndroidPolicy(ctx, enterpriseName, map[string]android.AgentManagedConfiguration{
|
|
hostUUID: {
|
|
ServerURL: appConfig.ServerSettings.ServerURL,
|
|
HostUUID: hostUUID,
|
|
EnrollSecret: enrollSecret,
|
|
},
|
|
})
|
|
if err != nil {
|
|
return ctxerr.Wrapf(ctx, err, "add fleet agent to android policy for host %s", hostUUID)
|
|
}
|
|
|
|
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",
|
|
}
|
|
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
|
|
}
|