Files
fleet/tools/android/android.go
T
Ian LittmanandVictor Lyuboslavsky 2f25580c3a 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 14:32:56 -06:00

267 lines
7.8 KiB
Go

package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"slices"
"strings"
"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 (
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() {
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)
}
// 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:
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) {
enterprises, err := mgmt.Enterprises.List().ProjectId(androidProjectID).Do()
if err != nil {
log.Fatalf("Error listing enterprises: %v", err)
}
if len(enterprises.Enterprises) == 0 {
log.Printf("No enterprises found")
return
}
for _, enterprise := range enterprises.Enterprises {
log.Printf("Enterprise: %+v", *enterprise)
}
}
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))
}
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)
}
}