mirror of
https://github.com/micromdm/micromdm/
synced 2026-08-05 00:45:50 +08:00
Tidy code for Go 1.20 and update Go version for Docker and CI --------- Co-authored-by: Kory Prince <korylprince@gmail.com>
368 lines
8.5 KiB
Go
368 lines
8.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"text/tabwriter"
|
|
|
|
"github.com/go-kit/kit/log"
|
|
"github.com/micromdm/micromdm/pkg/crypto"
|
|
"github.com/micromdm/micromdm/platform/blueprint"
|
|
"github.com/micromdm/micromdm/platform/device"
|
|
"github.com/micromdm/micromdm/platform/profile"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type getCommand struct {
|
|
config *ServerConfig
|
|
*remoteServices
|
|
}
|
|
|
|
func (cmd *getCommand) setup() error {
|
|
cfg, err := LoadServerConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.config = cfg
|
|
logger := log.NewLogfmtLogger(os.Stderr)
|
|
|
|
remote, err := setupClient(logger)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd.remoteServices = remote
|
|
return nil
|
|
}
|
|
|
|
func (cmd *getCommand) Run(args []string) error {
|
|
if len(args) < 1 {
|
|
cmd.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
if err := cmd.setup(); err != nil {
|
|
return err
|
|
}
|
|
|
|
var run func([]string) error
|
|
switch strings.ToLower(args[0]) {
|
|
case "dev", "device", "devices":
|
|
run = cmd.getDevices
|
|
case "dep-devices":
|
|
run = cmd.getDEPDevices
|
|
case "dep-account":
|
|
run = cmd.getDEPAccount
|
|
case "dep-profiles":
|
|
run = cmd.getDEPProfiles
|
|
case "dep-tokens":
|
|
run = cmd.getDepTokens
|
|
case "blueprints":
|
|
run = cmd.getBlueprints
|
|
case "profiles":
|
|
run = cmd.getProfiles
|
|
case "users":
|
|
run = cmd.getUsers
|
|
case "apps":
|
|
run = cmd.getApps
|
|
case "dep-autoassigners":
|
|
run = cmd.getDEPAutoAssigners
|
|
default:
|
|
cmd.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
return run(args[1:])
|
|
}
|
|
|
|
func (cmd *getCommand) Usage() error {
|
|
const getUsage = `
|
|
Display one or many resources.
|
|
|
|
Valid resource types:
|
|
|
|
* devices
|
|
* blueprints
|
|
* dep-tokens
|
|
* dep-devices
|
|
* dep-account
|
|
* dep-profiles
|
|
* dep-autoassigners
|
|
* users
|
|
* profiles
|
|
* apps
|
|
|
|
Examples:
|
|
# Get a list of devices
|
|
mdmctl get devices
|
|
|
|
# Get a device by serial (TODO implement filtering)
|
|
mdmctl get devices -serials=C02ABCDEF
|
|
|
|
`
|
|
fmt.Print(getUsage)
|
|
return nil
|
|
}
|
|
|
|
type devicesTableOutput struct{ w *tabwriter.Writer }
|
|
|
|
func (out *devicesTableOutput) BasicHeader() {
|
|
fmt.Fprintf(out.w, "UDID\tSerialNumber\tEnrollmentStatus\tLastSeen\n")
|
|
}
|
|
|
|
func (out *devicesTableOutput) BasicFooter() {
|
|
out.w.Flush()
|
|
}
|
|
|
|
func (cmd *getCommand) getDevices(args []string) error {
|
|
flagset := flag.NewFlagSet("devices", flag.ExitOnError)
|
|
var (
|
|
flFilterSerials = flagset.String("serials", "", "device serial, optionally comma-separated")
|
|
)
|
|
flagset.Usage = usageFor(flagset, "mdmctl get devices [flags]")
|
|
if err := flagset.Parse(args); err != nil {
|
|
return err
|
|
}
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
|
|
out := &devicesTableOutput{w}
|
|
out.BasicHeader()
|
|
defer out.BasicFooter()
|
|
ctx := context.Background()
|
|
|
|
// convert string to []string in case a filter serial is defined
|
|
// nil serials will return all serials from the database
|
|
var flFilterSerialsSlice []string
|
|
if *flFilterSerials != "" {
|
|
flFilterSerialsSlice = strings.Split(*flFilterSerials, ",")
|
|
}
|
|
|
|
devices, err := cmd.devicesvc.ListDevices(ctx, device.ListDevicesOption{FilterSerial: flFilterSerialsSlice})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, d := range devices {
|
|
fmt.Fprintf(out.w, "%s\t%s\t%v\t%s\n", d.UDID, d.SerialNumber, d.EnrollmentStatus, d.LastSeen)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
const defaultmdmctlFilesPath = "mdm-files"
|
|
|
|
func (cmd *getCommand) getDepTokens(args []string) error {
|
|
flagset := flag.NewFlagSet("dep-tokens", flag.ExitOnError)
|
|
var (
|
|
flFullCK = flagset.Bool("v", false, "Display full ConsumerKey in summary list")
|
|
flPublicKeyPath = flagset.String(
|
|
"export-public-key",
|
|
filepath.Join(defaultmdmctlFilesPath, "DEPPublicKey"),
|
|
"Filename of public key to write (to be uploaded to deploy.apple.com)",
|
|
)
|
|
flTokenPath = flagset.String(
|
|
"export-token",
|
|
filepath.Join(defaultmdmctlFilesPath, "DEPOAuthToken.json"),
|
|
"Filename to save decrypted oauth token (JSON)")
|
|
)
|
|
flagset.Usage = usageFor(flagset, "mdmctl get dep-tokens [flags]")
|
|
if err := flagset.Parse(args); err != nil {
|
|
return err
|
|
}
|
|
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
|
|
fmt.Fprintf(w, "ConsumerKey\tAccessTokenExpiry\n")
|
|
ctx := context.Background()
|
|
tokens, certBytes, err := cmd.configsvc.GetDEPTokens(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var ckTrimmed string
|
|
for _, t := range tokens {
|
|
if len(t.ConsumerKey) > 40 && !*flFullCK {
|
|
ckTrimmed = t.ConsumerKey[0:39] + "…"
|
|
} else {
|
|
ckTrimmed = t.ConsumerKey
|
|
}
|
|
fmt.Fprintf(w, "%s\t%s\n", ckTrimmed, t.AccessTokenExpiry.String())
|
|
}
|
|
w.Flush()
|
|
|
|
if *flPublicKeyPath != "" && certBytes != nil {
|
|
|
|
if err := os.MkdirAll(filepath.Dir(*flPublicKeyPath), 0755); err != nil {
|
|
return errors.Wrapf(err, "create directory %s", filepath.Dir(*flPublicKeyPath))
|
|
}
|
|
|
|
cert, err := x509.ParseCertificate(certBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = crypto.WritePEMCertificateFile(cert, *flPublicKeyPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("\nWrote DEP public key to: %s\n", *flPublicKeyPath)
|
|
}
|
|
|
|
if *flTokenPath != "" && len(tokens) > 0 {
|
|
t := tokens[0]
|
|
|
|
if err := os.MkdirAll(filepath.Dir(*flTokenPath), 0755); err != nil {
|
|
return errors.Wrapf(err, "create directory %s", filepath.Dir(*flTokenPath))
|
|
}
|
|
|
|
tokenFile, err := os.Create(*flTokenPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tokenFile.Close()
|
|
|
|
err = json.NewEncoder(tokenFile).Encode(t)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("\nWrote DEP token JSON to: %s\n", *flTokenPath)
|
|
if len(tokens) > 1 {
|
|
fmt.Println("WARNING: more than one DEP token returned; only saved first")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (cmd *getCommand) getBlueprints(args []string) error {
|
|
flagset := flag.NewFlagSet("blueprints", flag.ExitOnError)
|
|
var (
|
|
flBlueprintName = flagset.String("name", "", "name of blueprint")
|
|
flJSONName = flagset.String("f", "-", "filename of JSON to save to")
|
|
)
|
|
flagset.Usage = usageFor(flagset, "mdmctl get blueprints [flags]")
|
|
if err := flagset.Parse(args); err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx := context.Background()
|
|
blueprints, err := cmd.blueprintsvc.GetBlueprints(ctx, blueprint.GetBlueprintsOption{FilterName: *flBlueprintName})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if *flBlueprintName == "" || len(blueprints) < 1 {
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
|
|
fmt.Fprintf(w, "Name\tUUID\tManifests\tProfiles\tApply At\n")
|
|
for _, bp := range blueprints {
|
|
var applyAtStr string
|
|
if len(bp.ApplyAt) > 0 {
|
|
applyAtStr = strings.Join(bp.ApplyAt, ",")
|
|
} else {
|
|
applyAtStr = "(None)"
|
|
}
|
|
fmt.Fprintf(
|
|
w,
|
|
"%s\t%s\t%d\t%d\t%s\n",
|
|
bp.Name,
|
|
bp.UUID,
|
|
len(bp.ApplicationURLs),
|
|
len(bp.ProfileIdentifiers),
|
|
applyAtStr,
|
|
)
|
|
}
|
|
w.Flush()
|
|
} else if *flJSONName != "" {
|
|
bp := blueprints[0]
|
|
|
|
var output *os.File
|
|
{
|
|
if *flJSONName == "-" {
|
|
output = os.Stdout
|
|
} else {
|
|
var err error
|
|
output, err = os.Create(*flJSONName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer output.Close()
|
|
}
|
|
}
|
|
|
|
enc := json.NewEncoder(output)
|
|
enc.SetIndent("", " ")
|
|
err = enc.Encode(bp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if *flJSONName != "-" {
|
|
fmt.Printf("wrote blueprint %s to: %s\n", *flBlueprintName, *flJSONName)
|
|
if len(blueprints) > 1 {
|
|
fmt.Println("WARNING: more than one Blueprint returned; only saved first")
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (cmd *getCommand) getProfiles(args []string) error {
|
|
flagset := flag.NewFlagSet("profiles", flag.ExitOnError)
|
|
var (
|
|
flProfilePath = flagset.String("f", "-", "filename of profile to write")
|
|
flIdentifier = flagset.String("id", "", "profile Identifier")
|
|
)
|
|
flagset.Usage = usageFor(flagset, "mdmctl get profiles [flags]")
|
|
if err := flagset.Parse(args); err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx := context.Background()
|
|
profiles, err := cmd.profilesvc.GetProfiles(ctx, profile.GetProfilesOption{Identifier: *flIdentifier})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if *flIdentifier == "" || len(profiles) < 1 {
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
|
|
fmt.Fprintf(w, "Identifier\tLength\n")
|
|
for _, p := range profiles {
|
|
fmt.Fprintf(
|
|
w,
|
|
"%s\t%d\n",
|
|
p.Identifier,
|
|
len(p.Mobileconfig),
|
|
)
|
|
}
|
|
w.Flush()
|
|
} else if *flProfilePath != "" {
|
|
p := profiles[0]
|
|
|
|
var output *os.File
|
|
{
|
|
if *flProfilePath == "-" {
|
|
output = os.Stdout
|
|
} else {
|
|
var err error
|
|
output, err = os.Create(*flProfilePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer output.Close()
|
|
}
|
|
}
|
|
_, err = output.Write([]byte(p.Mobileconfig))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if *flProfilePath != "-" {
|
|
fmt.Printf("wrote profile id %s to: %s\n", p.Identifier, *flProfilePath)
|
|
if len(profiles) > 1 {
|
|
fmt.Println("WARNING: more than one Profile returned; only saved first")
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|