Files
micromdm/cmd/mdmctl/get.go
Jim Zajkowski 7a3715454a Chore: tidy for go 1.20 (#902)
Tidy code for Go 1.20 and update Go version for Docker and CI

---------

Co-authored-by: Kory Prince <korylprince@gmail.com>
2023-07-11 17:28:26 -05:00

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
}