fleet/tools/android/android.go

275 lines
8.1 KiB
Go
Raw Normal View History

package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"slices"
Install Fleet android agent on device enrollment. (#36050) <!-- 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
2025-11-21 20:42:24 +00:00
"strings"
Only allow FLEET_DEV_* env vars when `--dev` is passed, allow overriding configs one at a time in dev (#38652) Resolves #38484. This includes a CI job change to make sure we don't introduce any more env vars that don't get proxied (and thus turned off outside `--dev`). # 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`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. - [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 Manual QA touched hot paths, but did _not_ manually test every FLEET_DEV_* environment variable change. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Centralized dev-mode environment management for consistent FLEET_DEV_* handling and test-friendly overrides. * Dev-mode allows targeted overrides for certain dev-only configuration when running with --dev. * **Chores** * Migrated environment access to the centralized dev-mode helper across the codebase. * Added CI checks to enforce proper usage of FLEET_DEV_* variables. * **Documentation** * Added guidance on dev-mode environment variable rules and overrides. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Victor Lyuboslavsky <2685025+getvictor@users.noreply.github.com>
2026-01-27 20:32:56 +00:00
"github.com/fleetdm/fleet/v4/server/dev_mode"
"github.com/go-json-experiment/json"
"github.com/go-json-experiment/json/jsontext"
"google.golang.org/api/androidmanagement/v1"
"google.golang.org/api/option"
)
// Required env vars:
var (
Only allow FLEET_DEV_* env vars when `--dev` is passed, allow overriding configs one at a time in dev (#38652) Resolves #38484. This includes a CI job change to make sure we don't introduce any more env vars that don't get proxied (and thus turned off outside `--dev`). # 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`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. - [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 Manual QA touched hot paths, but did _not_ manually test every FLEET_DEV_* environment variable change. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Centralized dev-mode environment management for consistent FLEET_DEV_* handling and test-friendly overrides. * Dev-mode allows targeted overrides for certain dev-only configuration when running with --dev. * **Chores** * Migrated environment access to the centralized dev-mode helper across the codebase. * Added CI checks to enforce proper usage of FLEET_DEV_* variables. * **Documentation** * Added guidance on dev-mode environment variable rules and overrides. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Victor Lyuboslavsky <2685025+getvictor@users.noreply.github.com>
2026-01-27 20:32:56 +00:00
androidServiceCredentials string
androidProjectID string
)
const (
cmdEnterprisesDelete = "enterprises.delete"
cmdEnterprisesList = "enterprises.list"
cmdEnterprisesWebTokensCreate = "enterprises.webTokens.create"
cmdApplicationsGet = "applications.get"
cmdPoliciesList = "policies.list"
cmdPoliciesDelete = "policies.delete"
cmdDevicesList = "devices.list"
cmdDevicesDelete = "devices.delete"
cmdDevicesRelinquish = "devices.issueCommand.RELINQUISH_OWNERSHIP"
)
var commands = []string{
cmdEnterprisesDelete,
cmdEnterprisesList,
cmdEnterprisesWebTokensCreate,
cmdApplicationsGet,
cmdPoliciesList,
cmdPoliciesDelete,
cmdDevicesList,
cmdDevicesDelete,
cmdDevicesRelinquish,
}
func main() {
Only allow FLEET_DEV_* env vars when `--dev` is passed, allow overriding configs one at a time in dev (#38652) Resolves #38484. This includes a CI job change to make sure we don't introduce any more env vars that don't get proxied (and thus turned off outside `--dev`). # 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`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. - [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 Manual QA touched hot paths, but did _not_ manually test every FLEET_DEV_* environment variable change. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Centralized dev-mode environment management for consistent FLEET_DEV_* handling and test-friendly overrides. * Dev-mode allows targeted overrides for certain dev-only configuration when running with --dev. * **Chores** * Migrated environment access to the centralized dev-mode helper across the codebase. * Added CI checks to enforce proper usage of FLEET_DEV_* variables. * **Documentation** * Added guidance on dev-mode environment variable rules and overrides. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Victor Lyuboslavsky <2685025+getvictor@users.noreply.github.com>
2026-01-27 20:32:56 +00:00
dev_mode.IsEnabled = true
androidServiceCredentials = dev_mode.Env("FLEET_DEV_ANDROID_GOOGLE_SERVICE_CREDENTIALS")
if androidServiceCredentials == "" {
log.Fatal("FLEET_DEV_ANDROID_GOOGLE_SERVICE_CREDENTIALS must be set")
}
type credentials struct {
ProjectID string `json:"project_id"`
}
var creds credentials
err := json.Unmarshal([]byte(androidServiceCredentials), &creds)
if err != nil {
log.Fatalf("unmarshaling android service credentials: %s", err)
}
androidProjectID = creds.ProjectID
if androidProjectID == "" {
log.Fatal("project_id not found in android service credentials")
}
command := flag.String("command", "", strings.Join(commands, "\n"))
enterpriseID := flag.String("enterprise_id", "", "")
deviceID := flag.String("device_id", "", "")
policyID := flag.String("policy_id", "", "")
flag.Parse()
if !slices.Contains(commands, *command) {
flag.Usage()
os.Exit(1)
}
Install Fleet android agent on device enrollment. (#36050) <!-- 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
2025-11-21 20:42:24 +00:00
// Normalize enterprise_id by stripping "enterprises/" prefix if present
if *enterpriseID != "" {
*enterpriseID = strings.TrimPrefix(*enterpriseID, "enterprises/")
}
if slices.Index(commands, *command) == -1 {
log.Fatalf("Command must be one of: %s", strings.Join(commands, ", "))
}
ctx := context.Background()
mgmt, err := androidmanagement.NewService(ctx, option.WithCredentialsJSON([]byte(androidServiceCredentials)))
if err != nil {
log.Fatalf("Error creating android management service: %v", err)
}
switch *command {
case cmdEnterprisesDelete:
enterprisesDelete(mgmt, *enterpriseID)
case cmdEnterprisesList:
enterprisesList(mgmt)
case cmdEnterprisesWebTokensCreate:
Install Fleet android agent on device enrollment. (#36050) <!-- 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
2025-11-21 20:42:24 +00:00
enterprisesWebTokensCreate(mgmt, *enterpriseID)
case cmdApplicationsGet:
applicationsGet(mgmt, *enterpriseID, flag.Arg(0))
case cmdPoliciesList:
policiesList(mgmt, *enterpriseID)
case cmdPoliciesDelete:
policiesDelete(mgmt, *enterpriseID, *policyID)
case cmdDevicesList:
devicesList(mgmt, *enterpriseID)
case cmdDevicesDelete:
devicesDelete(mgmt, *enterpriseID, *deviceID)
case cmdDevicesRelinquish:
devicesRelinquishOwnership(mgmt, *enterpriseID, *deviceID)
default:
log.Fatalf("Unknown command: %s", *command)
}
}
func enterprisesDelete(mgmt *androidmanagement.Service, enterpriseID string) {
if enterpriseID == "" {
log.Fatalf("enterprise_id must be set")
}
_, err := mgmt.Enterprises.Delete("enterprises/" + enterpriseID).Do()
if err != nil {
log.Fatalf("Error deleting enterprise: %v", err)
}
}
func enterprisesList(mgmt *androidmanagement.Service) {
ctx := context.Background()
var enterprises []*androidmanagement.Enterprise
var callCount int
err := mgmt.Enterprises.List().ProjectId(androidProjectID).Pages(ctx, func(page *androidmanagement.ListEnterprisesResponse) error {
callCount++
enterprises = append(enterprises, page.Enterprises...)
return nil
})
if err != nil {
log.Fatalf("Error listing enterprises: %v", err)
}
if len(enterprises) == 0 {
log.Printf("No enterprises found")
return
}
for _, enterprise := range enterprises {
log.Printf("Enterprise: %+v", *enterprise)
}
log.Printf("%d enterprises found in %d pages", len(enterprises), callCount)
}
func policiesList(mgmt *androidmanagement.Service, enterpriseID string) {
if enterpriseID == "" {
log.Fatalf("enterprise_id must be set")
}
result, err := mgmt.Enterprises.Policies.List("enterprises/" + enterpriseID).Do()
if err != nil {
log.Fatalf("Error listing policies: %v", err)
}
if len(result.Policies) == 0 {
log.Printf("No policies found")
return
}
b, err := json.Marshal(result.Policies, jsontext.WithIndent(" "))
if err != nil {
log.Fatalf("Error marshalling policies: %v", err)
}
fmt.Println(string(b))
}
func applicationsGet(mgmt *androidmanagement.Service, enterpriseID string, applicationID string) {
if enterpriseID == "" {
log.Fatalf("enterprise_id must be set")
}
if applicationID == "" {
log.Fatal("application ID argument missing")
}
result, err := mgmt.Enterprises.Applications.Get(fmt.Sprintf("enterprises/%s/applications/%s", enterpriseID, applicationID)).Do()
if err != nil {
log.Fatalf("Error getting application: %v", err)
}
b, err := json.Marshal(result, jsontext.WithIndent(" "))
if err != nil {
log.Fatalf("Error marshalling application: %v", err)
}
fmt.Println(string(b))
}
func policiesDelete(mgmt *androidmanagement.Service, enterpriseID, policyID string) {
if enterpriseID == "" || policyID == "" {
log.Fatalf("enterprise_id and policy_id must be set")
}
_, err := mgmt.Enterprises.Policies.Delete("enterprises/" + enterpriseID + "/policies/" + policyID).Do()
if err != nil {
log.Fatalf("Error deleting policy: %v", err)
}
log.Printf("Policy %s deleted", policyID)
}
func devicesList(mgmt *androidmanagement.Service, enterpriseID string) {
if enterpriseID == "" {
log.Fatalf("enterprise_id must be set")
}
result, err := mgmt.Enterprises.Devices.List("enterprises/" + enterpriseID).Do()
if err != nil {
log.Fatalf("Error listing devices: %v", err)
}
if len(result.Devices) == 0 {
log.Printf("No policies found")
return
}
b, err := json.Marshal(result.Devices, jsontext.WithIndent(" "))
if err != nil {
log.Fatalf("Error marshalling devices: %v", err)
}
fmt.Println(string(b))
log.Printf("Total devices: %d", len(result.Devices))
}
func devicesDelete(mgmt *androidmanagement.Service, enterpriseID string, deviceID string) {
if enterpriseID == "" || deviceID == "" {
log.Fatalf("enterprise_id and device_id must be set")
}
_, err := mgmt.Enterprises.Devices.Delete("enterprises/" + enterpriseID + "/devices/" + deviceID).Do()
if err != nil {
log.Fatalf("Error listing devices: %v", err)
}
log.Printf("Device %s deleted", deviceID)
}
func devicesRelinquishOwnership(mgmt *androidmanagement.Service, enterpriseID, deviceID string) {
if enterpriseID == "" || deviceID == "" {
log.Fatalf("enterprise_id and device_id must be set")
}
operation, err := mgmt.Enterprises.Devices.IssueCommand("enterprises/"+enterpriseID+"/devices/"+deviceID, &androidmanagement.Command{
Type: "RELINQUISH_OWNERSHIP",
}).Do()
if err != nil {
log.Fatalf("Error issuing command: %v", err)
}
data, err := json.Marshal(operation, jsontext.WithIndent(" "))
if err != nil {
log.Fatalf("Error marshalling operation: %v", err)
}
log.Println(string(data))
}
Install Fleet android agent on device enrollment. (#36050) <!-- 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
2025-11-21 20:42:24 +00:00
func enterprisesWebTokensCreate(mgmt *androidmanagement.Service, enterpriseID string) {
if enterpriseID == "" {
log.Fatalf("enterprise_id must be set")
}
webToken := &androidmanagement.WebToken{
ParentFrameUrl: "https://example.com",
Permissions: []string{"APPROVE_APPS"},
}
result, err := mgmt.Enterprises.WebTokens.Create("enterprises/"+enterpriseID, webToken).Do()
if err != nil {
log.Fatalf("Error creating web token: %v", err)
}
data, err := json.Marshal(result, jsontext.WithIndent(" "))
if err != nil {
log.Fatalf("Error marshalling web token: %v", err)
}
log.Println(string(data))
// Construct and display the complete URL
if result.Value != "" {
playURL := "https://play.google.com/work/embedded/search?token=" + result.Value
log.Printf("\nComplete Play Store URL:\n%s\n", playURL)
}
}