Skip to content

Commit

Permalink
Things mostly working.
Browse files Browse the repository at this point in the history
  • Loading branch information
getvictor committed Jan 29, 2025
1 parent 0af94a9 commit b4add83
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 5 deletions.
6 changes: 6 additions & 0 deletions server/android/android.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,9 @@ func (e Enterprise) Name() string {
type EnrollmentToken struct {
Value string `json:"value"`
}

type Host struct {
HostID uint `db:"host_id"`
FleetEnterpriseID uint `db:"enterprise_id"`
DeviceID string `db:"device_id"`
}
3 changes: 3 additions & 0 deletions server/android/datastore.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ type Datastore interface {
GetEnterpriseByID(ctx context.Context, ID uint) (*Enterprise, error)
UpdateEnterprise(ctx context.Context, enterprise *Enterprise) error
ListEnterprises(ctx context.Context) ([]*Enterprise, error)

AddHost(ctx context.Context, host *Host) error
GetHost(ctx context.Context, fleetEnterpriseID uint, deviceID string) (*Host, error)
}

type MigrationStatus struct {
Expand Down
72 changes: 70 additions & 2 deletions server/android/job/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ package job
import (
"context"
"errors"
"fmt"
"os"
"strings"
"time"

"github.com/fleetdm/fleet/v4/server/android"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/fleet"
kitlog "github.com/go-kit/log"
"github.com/go-kit/log/level"
"google.golang.org/api/androidmanagement/v1"
"google.golang.org/api/option"
)
Expand Down Expand Up @@ -36,16 +40,80 @@ func ReconcileDevices(ctx context.Context, ds fleet.Datastore, androidDS android

for _, enterprise := range enterprises {
// Note: we can optimize this by using Fields to retrieve partial data https://developers.google.com/gdata/docs/2.0/basics#PartialResponse
// But actually this is not scalable for 100,000s devices, so we need to use PubSub.
devices, err := mgmt.Enterprises.Devices.List(enterprise.Name()).Do()
if err != nil {
return ctxerr.Wrap(ctx, err, "listing devices with Google API")
}

for _, device := range devices.Devices {
logger.Log("msg", "device", "device", device)
// Get the deviceId from the name: enterprises/{enterpriseId}/devices/{deviceId}
nameParts := strings.Split(device.Name, "/")
if len(nameParts) != 4 {
return ctxerr.Errorf(ctx, "invalid Android device name: %s", device.Name)
}
deviceID := nameParts[3]

host, err := androidDS.GetHost(ctx, enterprise.ID, deviceID)
if err != nil {
return ctxerr.Wrap(ctx, err, "getting host")
}
if host != nil {
// TODO: Update host if needed
continue
}

// TODO: Do EnrollHost and androidDS.AddHost inside a transaction so we don't add duplicate hosts
fleetHost, err := ds.EnrollHost(ctx, true, device.HardwareInfo.SerialNumber, device.HardwareInfo.SerialNumber,
device.HardwareInfo.SerialNumber, "", nil, 0)
if err != nil {
return ctxerr.Wrap(ctx, err, "enrolling host")
}
err = androidDS.AddHost(ctx, &android.Host{
FleetEnterpriseID: enterprise.ID,
DeviceID: deviceID,
HostID: fleetHost.ID,
})
if err != nil {
return ctxerr.Wrap(ctx, err, "adding Android host")
}

fleetHost.DiskEncryptionEnabled = &device.DeviceSettings.IsEncrypted
fleetHost.Platform = "ubuntu"
fleetHost.HardwareVendor = device.HardwareInfo.Manufacturer
fleetHost.HardwareModel = device.HardwareInfo.Model
fleetHost.OSVersion = "Android " + device.SoftwareInfo.AndroidVersion
lastEnrolledAt, err := time.Parse(time.RFC3339, device.EnrollmentTime)
switch {
case err != nil:
level.Error(logger).Log("msg", "parsing Android device last enrolled at", "err", err, "deviceId", deviceID)
default:
fleetHost.LastEnrolledAt = lastEnrolledAt
}
detailUpdatedAt, err := time.Parse(time.RFC3339, device.LastStatusReportTime)
switch {
case err != nil:
level.Error(logger).Log("msg", "parsing Android device detail updated at", "err", err, "deviceId", deviceID)
default:
fleetHost.DetailUpdatedAt = detailUpdatedAt
}
err = ds.UpdateHost(ctx, fleetHost)
if err != nil {
return ctxerr.Wrap(ctx, err, fmt.Sprintf("updating host with deviceId %s", deviceID))
}

err = ds.UpdateHostOperatingSystem(ctx, fleetHost.ID, fleet.OperatingSystem{
Name: "Android",
Version: device.SoftwareInfo.AndroidVersion,
Platform: "android",
KernelVersion: device.SoftwareInfo.DeviceKernelVersion,
})
if err != nil {
return ctxerr.Wrap(ctx, err, fmt.Sprintf("updating host operating system with deviceId %s", deviceID))
}

}

// For each device, check whether it is in Fleet. If not, add it
}

return nil
Expand Down
File renamed without changes.
File renamed without changes.
30 changes: 30 additions & 0 deletions server/android/mysql/hosts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package mysql

import (
"context"
"database/sql"
"errors"

"github.com/fleetdm/fleet/v4/server/android"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/jmoiron/sqlx"
)

func (ds *Datastore) GetHost(ctx context.Context, fleetEnterpriseID uint, deviceID string) (*android.Host, error) {
stmt := `SELECT enterprise_id, device_id, host_id FROM android_hosts WHERE enterprise_id = ? AND device_id = ?`
var host android.Host
err := sqlx.GetContext(ctx, ds.reader(ctx), &host, stmt, fleetEnterpriseID, deviceID)
switch {
case errors.Is(err, sql.ErrNoRows):
return nil, nil
case err != nil:
return nil, ctxerr.Wrap(ctx, err, "getting host")
}
return &host, nil
}

func (ds *Datastore) AddHost(ctx context.Context, host *android.Host) error {
stmt := `INSERT INTO android_hosts (enterprise_id, device_id, host_id) VALUES (?, ?, ?)`
_, err := ds.writer(ctx).ExecContext(ctx, stmt, host.FleetEnterpriseID, host.DeviceID, host.HostID)
return ctxerr.Wrap(ctx, err, "adding host")
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,29 @@ func init() {
}

func Up_20250101000000(tx *sql.Tx) error {
_, err := tx.Exec(`CREATE TABLE android_enterprises (
_, err := tx.Exec(`CREATE TABLE IF NOT EXISTS android_enterprises (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
signup_name VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
enterprise_id VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
signup_name VARCHAR(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
enterprise_id VARCHAR(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
created_at DATETIME(6) NULL DEFAULT NOW(6),
updated_at DATETIME(6) NULL DEFAULT NOW(6) ON UPDATE NOW(6))`)
if err != nil {
return fmt.Errorf("failed to create android_enterprise table: %w", err)
}

_, err = tx.Exec(`CREATE TABLE IF NOT EXISTS android_hosts (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
host_id INT UNSIGNED NULL,
enterprise_id INT UNSIGNED NOT NULL,
device_id VARCHAR(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
created_at DATETIME(6) NULL DEFAULT NOW(6),
updated_at DATETIME(6) NULL DEFAULT NOW(6) ON UPDATE NOW(6),
UNIQUE KEY idx_android_hosts_enterprise_device (enterprise_id, device_id) -- consider making this the primary key
)`)
if err != nil {
return fmt.Errorf("failed to create android_enterprise table: %w", err)
}

logger.Info.Println("Done with initial migration.")
return nil
}
Expand Down
5 changes: 5 additions & 0 deletions server/datastore/mysql/operating_systems.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ func (ds *Datastore) UpdateHostOperatingSystem(ctx context.Context, hostID uint,
if !updateNeeded {
return nil
}

const maxDisplayVersionLength = 10 // per DB schema
if len(hostOS.DisplayVersion) > maxDisplayVersionLength {
return ctxerr.Errorf(ctx, "host OS display version too long: %s", hostOS.DisplayVersion)
}
return ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
os, err := getOrGenerateOperatingSystemDB(ctx, tx, hostOS)
if err != nil {
Expand Down
20 changes: 20 additions & 0 deletions tools/android/android.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func main() {

command := flag.String("command", "", "")
enterpriseID := flag.String("enterprise_id", "", "")
deviceID := flag.String("device_id", "", "")
flag.Parse()

ctx := context.Background()
Expand All @@ -41,6 +42,8 @@ func main() {
policiesList(mgmt, *enterpriseID)
case "devices.list":
devicesList(mgmt, *enterpriseID)
case "devices.issueCommand.RELINQUISH_OWNERSHIP":
devicesRelinquishOwnership(mgmt, *enterpriseID, *deviceID)
default:
log.Fatalf("Unknown command: %s", *command)
}
Expand Down Expand Up @@ -108,3 +111,20 @@ func devicesList(mgmt *androidmanagement.Service, enterpriseID string) {
log.Println(string(data))
}
}

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.MarshalIndent(operation, "", " ")
if err != nil {
log.Fatalf("Error marshalling operation: %v", err)
}
log.Println(string(data))
}

0 comments on commit b4add83

Please sign in to comment.