mirror of
https://github.com/micromdm/micromdm/
synced 2026-08-12 04:55:39 +08:00
Move dep-token sub-command to mdmctl (#141)
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@@ -42,6 +45,8 @@ func (cmd *applyCommand) Run(args []string) error {
|
||||
switch strings.ToLower(args[0]) {
|
||||
case "blueprints":
|
||||
run = cmd.applyBlueprint
|
||||
case "dep-tokens":
|
||||
run = cmd.applyDEPTokens
|
||||
default:
|
||||
cmd.Usage()
|
||||
os.Exit(1)
|
||||
@@ -56,6 +61,7 @@ Apply a resource.
|
||||
Valid resource types:
|
||||
|
||||
* blueprints
|
||||
* dep-tokens
|
||||
|
||||
Examples:
|
||||
# Get a list of devices
|
||||
@@ -74,3 +80,31 @@ func (cmd *applyCommand) applyBlueprint(args []string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd *applyCommand) applyDEPTokens(args []string) error {
|
||||
flagset := flag.NewFlagSet("dep-tokens", flag.ExitOnError)
|
||||
var (
|
||||
flPublicKeyPath = flagset.String("import-token", "", "filename of p7m encrypted token file (downloaded from DEP portal)")
|
||||
)
|
||||
flagset.Usage = usageFor(flagset, "mdmctl apply dep-tokens [flags]")
|
||||
if err := flagset.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
if *flPublicKeyPath == "" {
|
||||
return errors.New("must provide -import-token parameter")
|
||||
}
|
||||
if _, err := os.Stat(*flPublicKeyPath); os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
p7mBytes, err := ioutil.ReadFile(*flPublicKeyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx := context.Background()
|
||||
err = cmd.applysvc.ApplyDEPToken(ctx, p7mBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("imported DEP token")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,12 +2,16 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
|
||||
"github.com/go-kit/kit/log"
|
||||
"github.com/micromdm/micromdm/core/list"
|
||||
)
|
||||
@@ -46,6 +50,8 @@ func (cmd *getCommand) Run(args []string) error {
|
||||
switch strings.ToLower(args[0]) {
|
||||
case "devices":
|
||||
run = cmd.getDevices
|
||||
case "dep-tokens":
|
||||
run = cmd.getDepTokens
|
||||
default:
|
||||
cmd.Usage()
|
||||
os.Exit(1)
|
||||
@@ -62,6 +68,7 @@ Valid resource types:
|
||||
|
||||
* devices
|
||||
* blueprints
|
||||
* dep-tokens
|
||||
|
||||
Examples:
|
||||
# Get a list of devices
|
||||
@@ -104,3 +111,84 @@ func (cmd *getCommand) getDevices(args []string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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", "", "filename of public key to write (to be uploaded to deploy.apple.com)")
|
||||
flTokenPath = flagset.String("export-token", "", "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.list.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 {
|
||||
cert, err := x509.ParseCertificate(certBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = 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]
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// TODO: move into crypto package and use for all main.savePEMCert() invocations
|
||||
func WritePEMCertificateFile(cert *x509.Certificate, path string) error {
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
return pem.Encode(
|
||||
file,
|
||||
&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: cert.Raw,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ func main() {
|
||||
cmd := &getCommand{}
|
||||
run = cmd.Run
|
||||
case "apply":
|
||||
cmd := &applyCommand{}
|
||||
run = cmd.Run
|
||||
default:
|
||||
usage()
|
||||
os.Exit(1)
|
||||
@@ -43,6 +45,7 @@ func usage() error {
|
||||
|
||||
Available Commands:
|
||||
get
|
||||
apply
|
||||
config
|
||||
version
|
||||
|
||||
|
||||
@@ -25,9 +25,19 @@ func NewClient(instance string, logger log.Logger, token string) (Service, error
|
||||
DecodeBlueprintRequest,
|
||||
).Endpoint()
|
||||
}
|
||||
var applyDEPTokensEndpoint endpoint.Endpoint
|
||||
{
|
||||
applyDEPTokensEndpoint = httptransport.NewClient(
|
||||
"PUT",
|
||||
copyURL(u, "/v1/dep-tokens"),
|
||||
encodeRequestWithToken(token, EncodeHTTPGenericRequest),
|
||||
DecodeDEPTokensRequest,
|
||||
).Endpoint()
|
||||
}
|
||||
|
||||
return Endpoints{
|
||||
ApplyBlueprintEndpoint: applyBlueprintEndpoint,
|
||||
ApplyDEPTokensEndpoint: applyDEPTokensEndpoint,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
type Endpoints struct {
|
||||
ApplyBlueprintEndpoint endpoint.Endpoint
|
||||
ApplyDEPTokensEndpoint endpoint.Endpoint
|
||||
}
|
||||
|
||||
func (e Endpoints) ApplyBlueprint(ctx context.Context, bp *blueprint.Blueprint) error {
|
||||
@@ -20,6 +21,15 @@ func (e Endpoints) ApplyBlueprint(ctx context.Context, bp *blueprint.Blueprint)
|
||||
return resp.(blueprintResponse).Err
|
||||
}
|
||||
|
||||
func (e Endpoints) ApplyDEPToken(ctx context.Context, P7MContent []byte) error {
|
||||
req := depTokensRequest{P7MContent: P7MContent}
|
||||
resp, err := e.ApplyDEPTokensEndpoint(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return resp.(depTokensResponse).Err
|
||||
}
|
||||
|
||||
func MakeApplyBlueprintEndpoint(svc Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
|
||||
req := request.(blueprintRequest)
|
||||
@@ -30,6 +40,16 @@ func MakeApplyBlueprintEndpoint(svc Service) endpoint.Endpoint {
|
||||
}
|
||||
}
|
||||
|
||||
func MakeApplyDEPTokensEndpoint(svc Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
|
||||
req := request.(depTokensRequest)
|
||||
err = svc.ApplyDEPToken(ctx, req.P7MContent)
|
||||
return depTokensResponse{
|
||||
Err: err,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
type blueprintRequest struct {
|
||||
Blueprint *blueprint.Blueprint `json:"blueprint"`
|
||||
}
|
||||
@@ -37,3 +57,13 @@ type blueprintRequest struct {
|
||||
type blueprintResponse struct {
|
||||
Err error `json:"err,omitempty"`
|
||||
}
|
||||
|
||||
type depTokensRequest struct {
|
||||
P7MContent []byte `json:"p7m_content"`
|
||||
}
|
||||
|
||||
type depTokensResponse struct {
|
||||
Err error `json:"err,omitempty"`
|
||||
}
|
||||
|
||||
func (r depTokensResponse) error() error { return r.Err }
|
||||
|
||||
@@ -2,18 +2,116 @@ package apply
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"github.com/fullsailor/pkcs7"
|
||||
"io"
|
||||
"net/textproto"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/micromdm/micromdm/blueprint"
|
||||
"github.com/micromdm/micromdm/core/list"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
ApplyBlueprint(ctx context.Context, bp *blueprint.Blueprint) error
|
||||
ApplyDEPToken(ctx context.Context, P7MContent []byte) error
|
||||
}
|
||||
|
||||
type ApplyService struct {
|
||||
Blueprints *blueprint.DB
|
||||
DB *bolt.DB // TODO: replace with reference to DEP token svc/pkg
|
||||
}
|
||||
|
||||
func (svc *ApplyService) ApplyBlueprint(ctx context.Context, bp *blueprint.Blueprint) error {
|
||||
return svc.Blueprints.Save(bp)
|
||||
}
|
||||
|
||||
// unwrapSMIME removes the S/MIME-like wrapper around raw CMS/PKCS7 data
|
||||
func unwrapSMIME(smime []byte) ([]byte, error) {
|
||||
tr := textproto.NewReader(bufio.NewReader(bytes.NewReader(smime)))
|
||||
if _, err := tr.ReadMIMEHeader(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dec := base64.NewDecoder(base64.StdEncoding, tr.DotReader())
|
||||
buf := new(bytes.Buffer)
|
||||
io.Copy(buf, dec)
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// unwrapTokenJSON removes the MIME-like headers and text surrounding the DEP token JSON
|
||||
func unwrapTokenJSON(wrapped []byte) ([]byte, error) {
|
||||
tr := textproto.NewReader(bufio.NewReader(bytes.NewReader(wrapped)))
|
||||
if _, err := tr.ReadMIMEHeader(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tokenJSON := new(bytes.Buffer)
|
||||
for {
|
||||
line, err := tr.ReadLineBytes()
|
||||
if err != nil && err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
line = bytes.Trim(line, "-----BEGIN MESSAGE-----")
|
||||
line = bytes.Trim(line, "-----END MESSAGE-----")
|
||||
if _, err := tokenJSON.Write(line); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return tokenJSON.Bytes(), nil
|
||||
}
|
||||
|
||||
// TODO: move into seperate svc/pkg
|
||||
const (
|
||||
depTokenBucket = "mdm.DEPToken"
|
||||
)
|
||||
|
||||
func PutDEPToken(db *bolt.DB, consumerKey string, json []byte) error {
|
||||
err := db.Update(func(tx *bolt.Tx) error {
|
||||
b, err := tx.CreateBucketIfNotExists([]byte(depTokenBucket))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.Put([]byte(consumerKey), json)
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (svc *ApplyService) ApplyDEPToken(ctx context.Context, P7MContent []byte) error {
|
||||
unwrapped, err := unwrapSMIME(P7MContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key, cert, err := list.GetDEPKeypair(svc.DB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p7, err := pkcs7.Parse(unwrapped)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
decrypted, err := p7.Decrypt(cert, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tokenJSON, err := unwrapTokenJSON(decrypted)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var depToken list.DEPToken
|
||||
err = json.Unmarshal(tokenJSON, &depToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = PutDEPToken(svc.DB, depToken.ConsumerKey, tokenJSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("stored DEP token with ck", depToken.ConsumerKey)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -12,17 +12,37 @@ import (
|
||||
"github.com/micromdm/micromdm/blueprint"
|
||||
)
|
||||
|
||||
func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptransport.ServerOption) http.Handler {
|
||||
var h http.Handler
|
||||
h = httptransport.NewServer(
|
||||
endpoints.ApplyBlueprintEndpoint,
|
||||
decodeBlueprintRequest,
|
||||
encodeResponse,
|
||||
opts...,
|
||||
)
|
||||
type HTTPHandlers struct {
|
||||
BlueprintHandler http.Handler
|
||||
DEPTokensHandler http.Handler
|
||||
}
|
||||
|
||||
func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptransport.ServerOption) HTTPHandlers {
|
||||
h := HTTPHandlers{
|
||||
BlueprintHandler: httptransport.NewServer(
|
||||
endpoints.ApplyBlueprintEndpoint,
|
||||
decodeBlueprintRequest,
|
||||
encodeResponse,
|
||||
opts...,
|
||||
),
|
||||
DEPTokensHandler: httptransport.NewServer(
|
||||
endpoints.ApplyDEPTokensEndpoint,
|
||||
decodeDEPTokensRequest,
|
||||
encodeResponse,
|
||||
opts...,
|
||||
),
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func decodeDEPTokensRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
var req depTokensRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeBlueprintRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
var bp blueprint.Blueprint
|
||||
if err := json.NewDecoder(r.Body).Decode(&bp); err != nil {
|
||||
@@ -62,7 +82,10 @@ func encodeResponse(ctx context.Context, w http.ResponseWriter, response interfa
|
||||
}
|
||||
|
||||
func EncodeError(ctx context.Context, err error, w http.ResponseWriter) {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
enc := json.NewEncoder(w)
|
||||
enc.SetIndent("", " ")
|
||||
enc.Encode(errorWrapper{Error: err.Error()})
|
||||
}
|
||||
|
||||
// EncodeHTTPGenericRequest is a transport/http.EncodeRequestFunc that
|
||||
@@ -84,3 +107,12 @@ func DecodeBlueprintRequest(_ context.Context, r *http.Response) (interface{}, e
|
||||
err := json.NewDecoder(r.Body).Decode(&resp)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func DecodeDEPTokensRequest(_ context.Context, r *http.Response) (interface{}, error) {
|
||||
if r.StatusCode != http.StatusOK {
|
||||
return nil, errorDecoder(r)
|
||||
}
|
||||
var resp depTokensResponse
|
||||
err := json.NewDecoder(r.Body).Decode(&resp)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
@@ -25,9 +25,19 @@ func NewClient(instance string, logger log.Logger, token string) (Service, error
|
||||
DecodeDevicesResponse,
|
||||
).Endpoint()
|
||||
}
|
||||
var getDEPTokensEndpoint endpoint.Endpoint
|
||||
{
|
||||
getDEPTokensEndpoint = httptransport.NewClient(
|
||||
"GET",
|
||||
copyURL(u, "/v1/dep-tokens"),
|
||||
encodeRequestWithToken(token, EncodeHTTPGenericRequest),
|
||||
DecodeGetDEPTokensResponse,
|
||||
).Endpoint()
|
||||
}
|
||||
|
||||
return Endpoints{
|
||||
ListDevicesEndpoint: listDevicesEndpoint,
|
||||
ListDevicesEndpoint: listDevicesEndpoint,
|
||||
GetDEPTokensEndpoint: getDEPTokensEndpoint,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ import (
|
||||
)
|
||||
|
||||
type Endpoints struct {
|
||||
ListDevicesEndpoint endpoint.Endpoint
|
||||
ListDevicesEndpoint endpoint.Endpoint
|
||||
GetDEPTokensEndpoint endpoint.Endpoint
|
||||
}
|
||||
|
||||
func (e Endpoints) ListDevices(ctx context.Context, opts ListDevicesOption) ([]DeviceDTO, error) {
|
||||
@@ -20,6 +21,14 @@ func (e Endpoints) ListDevices(ctx context.Context, opts ListDevicesOption) ([]D
|
||||
return response.(devicesResponse).Devices, response.(devicesResponse).Err
|
||||
}
|
||||
|
||||
func (e Endpoints) GetDEPTokens(ctx context.Context) ([]DEPToken, []byte, error) {
|
||||
resp, err := e.GetDEPTokensEndpoint(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return resp.(depTokenResponse).DEPTokens, resp.(depTokenResponse).DEPPubKey, nil
|
||||
}
|
||||
|
||||
func MakeListDevicesEndpoint(svc Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
|
||||
req := request.(devicesRequest)
|
||||
@@ -31,6 +40,17 @@ func MakeListDevicesEndpoint(svc Service) endpoint.Endpoint {
|
||||
}
|
||||
}
|
||||
|
||||
func MakeGetDEPTokensEndpoint(svc Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
|
||||
tokens, pubkey, err := svc.GetDEPTokens(ctx)
|
||||
return depTokenResponse{
|
||||
DEPTokens: tokens,
|
||||
DEPPubKey: pubkey,
|
||||
Err: err,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
type DeviceDTO struct {
|
||||
SerialNumber string `json:"serial_number"`
|
||||
UDID string `json:"udid"`
|
||||
@@ -43,3 +63,17 @@ type devicesResponse struct {
|
||||
Devices []DeviceDTO `json:"devices"`
|
||||
Err error `json:"err,omitempty"`
|
||||
}
|
||||
|
||||
type DEPToken struct {
|
||||
ConsumerKey string `json:"consumer_key"`
|
||||
ConsumerSecret string `json:"consumer_secret"`
|
||||
AccessToken string `json:"access_token"`
|
||||
AccessSecret string `json:"access_secret"`
|
||||
AccessTokenExpiry time.Time `json:"access_token_expiry"`
|
||||
}
|
||||
|
||||
type depTokenResponse struct {
|
||||
DEPTokens []DEPToken `json:"dep_tokens"`
|
||||
DEPPubKey []byte `json:"public_key"`
|
||||
Err error `json:"err,omitempty"`
|
||||
}
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"math/big"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
|
||||
"github.com/micromdm/micromdm/device"
|
||||
)
|
||||
@@ -16,10 +27,12 @@ type ListDevicesOption struct {
|
||||
|
||||
type Service interface {
|
||||
ListDevices(ctx context.Context, opt ListDevicesOption) ([]DeviceDTO, error)
|
||||
GetDEPTokens(ctx context.Context) ([]DEPToken, []byte, error)
|
||||
}
|
||||
|
||||
type ListService struct {
|
||||
Devices *device.DB
|
||||
DB *bolt.DB // TODO: replace with reference to DEP token svc/pkg
|
||||
}
|
||||
|
||||
func (svc *ListService) ListDevices(ctx context.Context, opt ListDevicesOption) ([]DeviceDTO, error) {
|
||||
@@ -35,3 +48,142 @@ func (svc *ListService) ListDevices(ctx context.Context, opt ListDevicesOption)
|
||||
}
|
||||
return dto, err
|
||||
}
|
||||
|
||||
// TODO: move into seperate svc/pkg
|
||||
const (
|
||||
depTokenBucket = "mdm.DEPToken"
|
||||
)
|
||||
|
||||
// TODO: move into seperate svc/pkg
|
||||
func GetDEPTokens(db *bolt.DB) ([]DEPToken, error) {
|
||||
var result []DEPToken
|
||||
err := db.View(func(tx *bolt.Tx) error {
|
||||
c := tx.Bucket([]byte(depTokenBucket)).Cursor()
|
||||
|
||||
prefix := []byte("CK_")
|
||||
for k, v := c.Seek(prefix); k != nil && bytes.HasPrefix(k, prefix); k, v = c.Next() {
|
||||
var depToken DEPToken
|
||||
err := json.Unmarshal(v, &depToken)
|
||||
if err != nil {
|
||||
// TODO: log problematic DEP token, or remove altogether?
|
||||
continue
|
||||
}
|
||||
result = append(result, depToken)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
// TODO: move into seperate svc/pkg
|
||||
func generateAndStoreDEPKeypair(db *bolt.DB) (key *rsa.PrivateKey, cert *x509.Certificate, err error) {
|
||||
key, cert, err = SimpleSelfSignedRSAKeypair("micromdm-dep-token", 365)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pkBytes := x509.MarshalPKCS1PrivateKey(key)
|
||||
certBytes := cert.Raw
|
||||
|
||||
err = db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket([]byte(depTokenBucket))
|
||||
err := b.Put([]byte("key"), pkBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = b.Put([]byte("certificate"), certBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: move into seperate svc/pkg
|
||||
func GetDEPKeypair(db *bolt.DB) (key *rsa.PrivateKey, cert *x509.Certificate, err error) {
|
||||
var keyBytes, certBytes []byte
|
||||
err = db.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket([]byte(depTokenBucket))
|
||||
keyBytes = b.Get([]byte("key"))
|
||||
certBytes = b.Get([]byte("certificate"))
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if keyBytes == nil || certBytes == nil {
|
||||
// if there is no certificate or private key then generate
|
||||
key, cert, err = generateAndStoreDEPKeypair(db)
|
||||
} else {
|
||||
key, err = x509.ParsePKCS1PrivateKey(keyBytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cert, err = x509.ParseCertificate(certBytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (svc *ListService) GetDEPTokens(ctx context.Context) ([]DEPToken, []byte, error) {
|
||||
_, cert, err := GetDEPKeypair(svc.DB)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var certBytes []byte
|
||||
if cert != nil {
|
||||
certBytes = cert.Raw
|
||||
}
|
||||
|
||||
tokens, err := GetDEPTokens(svc.DB)
|
||||
if err != nil {
|
||||
return nil, certBytes, err
|
||||
}
|
||||
|
||||
return tokens, certBytes, nil
|
||||
}
|
||||
|
||||
// TODO: move into crypto package
|
||||
func RandomCertificateSerialNumber() (*big.Int, error) {
|
||||
limit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
return rand.Int(rand.Reader, limit)
|
||||
}
|
||||
|
||||
// TODO: move into crypto package
|
||||
func SimpleSelfSignedRSAKeypair(cn string, days int) (key *rsa.PrivateKey, cert *x509.Certificate, err error) {
|
||||
key, err = rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
serialNumber, err := RandomCertificateSerialNumber()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
timeNow := time.Now()
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
CommonName: cn,
|
||||
},
|
||||
NotBefore: timeNow,
|
||||
NotAfter: timeNow.Add(time.Duration(days) * 24 * time.Hour),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cert, err = x509.ParseCertificate(certBytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -11,17 +11,32 @@ import (
|
||||
httptransport "github.com/go-kit/kit/transport/http"
|
||||
)
|
||||
|
||||
func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptransport.ServerOption) http.Handler {
|
||||
var h http.Handler
|
||||
h = httptransport.NewServer(
|
||||
endpoints.ListDevicesEndpoint,
|
||||
decodeListDevicesRequest,
|
||||
encodeResponse,
|
||||
opts...,
|
||||
)
|
||||
type HTTPHandlers struct {
|
||||
ListDevicesHandler http.Handler
|
||||
GetDEPTokensHandler http.Handler
|
||||
}
|
||||
|
||||
func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptransport.ServerOption) HTTPHandlers {
|
||||
h := HTTPHandlers{
|
||||
ListDevicesHandler: httptransport.NewServer(
|
||||
endpoints.ListDevicesEndpoint,
|
||||
decodeListDevicesRequest,
|
||||
encodeResponse,
|
||||
opts...,
|
||||
),
|
||||
GetDEPTokensHandler: httptransport.NewServer(
|
||||
endpoints.GetDEPTokensEndpoint,
|
||||
decodeGetDEPTokensRequest,
|
||||
encodeResponse,
|
||||
opts...),
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func decodeGetDEPTokensRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func decodeListDevicesRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
req := devicesRequest{
|
||||
Opts: ListDevicesOption{},
|
||||
@@ -79,3 +94,12 @@ func DecodeDevicesResponse(_ context.Context, r *http.Response) (interface{}, er
|
||||
err := json.NewDecoder(r.Body).Decode(&resp)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func DecodeGetDEPTokensResponse(_ context.Context, r *http.Response) (interface{}, error) {
|
||||
if r.StatusCode != http.StatusOK {
|
||||
return nil, errorDecoder(r)
|
||||
}
|
||||
var resp depTokenResponse
|
||||
err := json.NewDecoder(r.Body).Decode(&resp)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
274
deptoken.go
274
deptoken.go
@@ -1,274 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"math/big"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/fullsailor/pkcs7"
|
||||
"github.com/micromdm/dep"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
depTokenRSAKeyFilename = "deptoken.key"
|
||||
depTokenCertFilename = "deptoken.pem"
|
||||
depTokenBucket = "mdm.DEPToken"
|
||||
)
|
||||
|
||||
type DEPTokenJSON struct {
|
||||
ConsumerKey string `json:"consumer_key"`
|
||||
ConsumerSecret string `json:"consumer_secret"`
|
||||
AccessToken string `json:"access_token"`
|
||||
AccessSecret string `json:"access_secret"`
|
||||
AccessTokenExpiry time.Time `json:"access_token_expiry"`
|
||||
}
|
||||
|
||||
func depToken(args []string) error {
|
||||
flagset := flag.NewFlagSet("dep-token", flag.ExitOnError)
|
||||
var (
|
||||
flPublicKey = flagset.String("export-public-key", "", "filename of public key to write (to be uploaded to deploy.apple.com)")
|
||||
flImportToken = flagset.String("import-token", "", "filename of p7m encrypted token file (downloaded from DEP portal)")
|
||||
flExportToken = flagset.String("export-token", "", "filename to save decrypted oauth token JSON")
|
||||
)
|
||||
flagset.Usage = usageFor(flagset, "micromdm dep-token [flags]")
|
||||
if err := flagset.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keyPath := path.Join(configDBPath, depTokenRSAKeyFilename)
|
||||
var pk *rsa.PrivateKey
|
||||
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
|
||||
// key doesn't yet exist, make it
|
||||
pk, err = rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = savePEMKey(keyPath, pk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// key exists, load it
|
||||
pemKey, err := ioutil.ReadFile(keyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
block, _ := pem.Decode(pemKey)
|
||||
|
||||
if block == nil || block.Type != "RSA PRIVATE KEY" {
|
||||
return errors.New("invalid DEP token private key")
|
||||
}
|
||||
|
||||
if pk, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// fmt.Println("loaded key", keyPath)
|
||||
}
|
||||
|
||||
certPath := path.Join(configDBPath, depTokenCertFilename)
|
||||
var cert []byte
|
||||
if _, err := os.Stat(certPath); os.IsNotExist(err) {
|
||||
// cert doesn't yet exist, make it
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
CommonName: "micromdm-dep-token",
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(365 * 24 * time.Hour),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
cert, err := x509.CreateCertificate(rand.Reader, &template, &template, &pk.PublicKey, pk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
certOut, err := os.Create(certPath)
|
||||
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: cert})
|
||||
certOut.Close()
|
||||
|
||||
// fmt.Println("generated and saved cert", certPath)
|
||||
} else {
|
||||
// cert exists, load it
|
||||
pemCert, err := ioutil.ReadFile(certPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
block, _ := pem.Decode(pemCert)
|
||||
|
||||
if block == nil || block.Type != "CERTIFICATE" {
|
||||
return errors.New("invalid DEP token cert")
|
||||
}
|
||||
|
||||
cert = block.Bytes
|
||||
|
||||
if _, err = x509.ParseCertificate(cert); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if *flPublicKey == "" && *flImportToken == "" && *flExportToken == "" {
|
||||
flagset.Usage()
|
||||
return nil
|
||||
}
|
||||
|
||||
if *flPublicKey != "" {
|
||||
if _, err := os.Stat(certPath); os.IsExist(err) {
|
||||
return errors.New("public key filename already exists, please choose another")
|
||||
}
|
||||
certOut, err := os.Create(*flPublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer certOut.Close()
|
||||
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: cert}); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("wrote", *flPublicKey)
|
||||
}
|
||||
|
||||
if *flImportToken != "" {
|
||||
f, err := os.Open(*flImportToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
tr := textproto.NewReader(bufio.NewReader(f))
|
||||
if _, err := tr.ReadMIMEHeader(); err != nil {
|
||||
return err
|
||||
}
|
||||
dec := base64.NewDecoder(base64.StdEncoding, tr.DotReader())
|
||||
buf := new(bytes.Buffer)
|
||||
io.Copy(buf, dec)
|
||||
p7, err := pkcs7.Parse(buf.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parsedCert, err := x509.ParseCertificate(cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
decrypted, err := p7.Decrypt(parsedCert, pk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// the contained decrypted data is also wrapped in a textproto-like
|
||||
// wrapper. strip it, too.
|
||||
|
||||
tr = textproto.NewReader(bufio.NewReader(bytes.NewReader(decrypted)))
|
||||
if _, err := tr.ReadMIMEHeader(); err != nil {
|
||||
return err
|
||||
}
|
||||
tokenJSON := new(bytes.Buffer)
|
||||
for {
|
||||
line, err := tr.ReadLineBytes()
|
||||
if err != nil && err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
line = bytes.Trim(line, "-----BEGIN MESSAGE-----")
|
||||
line = bytes.Trim(line, "-----END MESSAGE-----")
|
||||
if _, err := tokenJSON.Write(line); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var depToken DEPTokenJSON
|
||||
err = json.Unmarshal(tokenJSON.Bytes(), &depToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// copy over values
|
||||
depConfig := &dep.Config{}
|
||||
depConfig.ConsumerKey = depToken.ConsumerKey
|
||||
depConfig.ConsumerSecret = depToken.ConsumerSecret
|
||||
depConfig.AccessToken = depToken.AccessToken
|
||||
depConfig.AccessSecret = depToken.AccessSecret
|
||||
|
||||
sm := &config{}
|
||||
sm.setupBolt()
|
||||
if sm.err != nil {
|
||||
return sm.err
|
||||
}
|
||||
|
||||
err = sm.db.Update(func(tx *bolt.Tx) error {
|
||||
b, err := tx.CreateBucketIfNotExists([]byte(depTokenBucket))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.Put([]byte(depConfig.ConsumerKey), tokenJSON.Bytes())
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("saved token", depConfig.ConsumerKey)
|
||||
}
|
||||
|
||||
if *flExportToken != "" {
|
||||
sm := &config{}
|
||||
sm.setupBolt()
|
||||
if sm.err != nil {
|
||||
return sm.err
|
||||
}
|
||||
|
||||
err := sm.db.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket([]byte(depTokenBucket))
|
||||
if b == nil {
|
||||
fmt.Println("no DEP server token found. using depsim")
|
||||
return nil
|
||||
}
|
||||
_, v := b.Cursor().First()
|
||||
if v == nil {
|
||||
return errors.New("no dep token found. did you import it?")
|
||||
}
|
||||
f, err := os.Create(*flExportToken)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create file to save DEP token")
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err := f.Write(v); err != nil {
|
||||
return errors.Wrap(err, "saving DEP token JSON")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
}
|
||||
fmt.Println("saved oauth token file")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
3
main.go
3
main.go
@@ -28,8 +28,6 @@ func main() {
|
||||
return
|
||||
case "serve":
|
||||
run = serve
|
||||
case "dep-token":
|
||||
run = depToken
|
||||
case "get":
|
||||
run = getResource
|
||||
case "apply":
|
||||
@@ -55,7 +53,6 @@ Available Commands:
|
||||
dev
|
||||
get
|
||||
apply
|
||||
dep-token
|
||||
version
|
||||
|
||||
Use micromdm <command> -h for additional usage of each command.
|
||||
|
||||
60
serve.go
60
serve.go
@@ -8,7 +8,6 @@ import (
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"flag"
|
||||
"fmt"
|
||||
@@ -193,7 +192,7 @@ func serve(args []string) error {
|
||||
|
||||
var listsvc list.Service
|
||||
{
|
||||
listsvc = &list.ListService{Devices: devDB}
|
||||
listsvc = &list.ListService{Devices: devDB, DB: sm.db}
|
||||
}
|
||||
var listDevicesEndpoint endpoint.Endpoint
|
||||
{
|
||||
@@ -201,12 +200,13 @@ func serve(args []string) error {
|
||||
|
||||
}
|
||||
listEndpoints := list.Endpoints{
|
||||
ListDevicesEndpoint: listDevicesEndpoint,
|
||||
ListDevicesEndpoint: listDevicesEndpoint,
|
||||
GetDEPTokensEndpoint: list.MakeGetDEPTokensEndpoint(listsvc),
|
||||
}
|
||||
|
||||
var applysvc apply.Service
|
||||
{
|
||||
applysvc = &apply.ApplyService{Blueprints: bpDB}
|
||||
applysvc = &apply.ApplyService{Blueprints: bpDB, DB: sm.db}
|
||||
}
|
||||
|
||||
var applyBlueprintEndpoint endpoint.Endpoint
|
||||
@@ -216,6 +216,7 @@ func serve(args []string) error {
|
||||
|
||||
applyEndpoints := apply.Endpoints{
|
||||
ApplyBlueprintEndpoint: applyBlueprintEndpoint,
|
||||
ApplyDEPTokensEndpoint: apply.MakeApplyDEPTokensEndpoint(applysvc),
|
||||
}
|
||||
|
||||
applyAPIHandlers := apply.MakeHTTPHandlers(ctx, applyEndpoints, connectOpts...)
|
||||
@@ -242,8 +243,10 @@ func serve(args []string) error {
|
||||
|
||||
// API commands. Only handled if the user provides an api key.
|
||||
if *flAPIKey != "" {
|
||||
r.Handle("/v1/devices", apiAuthMiddleware(*flAPIKey, listAPIHandlers)).Methods("GET")
|
||||
r.Handle("/v1/blueprints", apiAuthMiddleware(*flAPIKey, applyAPIHandlers)).Methods("PUT")
|
||||
r.Handle("/v1/devices", apiAuthMiddleware(*flAPIKey, listAPIHandlers.ListDevicesHandler)).Methods("GET")
|
||||
r.Handle("/v1/dep-tokens", apiAuthMiddleware(*flAPIKey, listAPIHandlers.GetDEPTokensHandler)).Methods("GET")
|
||||
r.Handle("/v1/dep-tokens", apiAuthMiddleware(*flAPIKey, applyAPIHandlers.DEPTokensHandler)).Methods("PUT")
|
||||
r.Handle("/v1/blueprints", apiAuthMiddleware(*flAPIKey, applyAPIHandlers.BlueprintHandler)).Methods("PUT")
|
||||
}
|
||||
|
||||
if *flRepoPath != "" {
|
||||
@@ -601,6 +604,22 @@ func (c *config) depClient() (dep.Client, error) {
|
||||
// depsim config
|
||||
depsim := c.depsim
|
||||
var conf *dep.Config
|
||||
|
||||
// try getting the oauth config from bolt
|
||||
tokens, err := list.GetDEPTokens(c.db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(tokens) >= 1 {
|
||||
conf = new(dep.Config)
|
||||
conf.ConsumerSecret = tokens[0].ConsumerSecret
|
||||
conf.ConsumerKey = tokens[0].ConsumerKey
|
||||
conf.AccessSecret = tokens[0].AccessSecret
|
||||
conf.AccessToken = tokens[0].AccessToken
|
||||
// TODO: handle expiration
|
||||
}
|
||||
|
||||
// override with depsim keys if specified on CLI
|
||||
if depsim {
|
||||
conf = &dep.Config{
|
||||
ConsumerKey: "CK_48dd68d198350f51258e885ce9a5c37ab7f98543c4a697323d75682a6c10a32501cb247e3db08105db868f73f2c972bdb6ae77112aea803b9219eb52689d42e6",
|
||||
@@ -610,40 +629,13 @@ func (c *config) depClient() (dep.Client, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// try getting the oauth config from bolt
|
||||
|
||||
err := c.db.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket([]byte(depTokenBucket))
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
_, v := b.Cursor().First()
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
var token DEPTokenJSON
|
||||
err := json.Unmarshal(v, &token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conf = new(dep.Config)
|
||||
conf.ConsumerSecret = token.ConsumerSecret
|
||||
conf.ConsumerKey = token.ConsumerKey
|
||||
conf.AccessSecret = token.AccessSecret
|
||||
conf.AccessToken = token.AccessToken
|
||||
// TODO handle expiration.
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if conf == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
depServerURL := "https://mdmenrollment.apple.com"
|
||||
if depsim {
|
||||
// TODO: support supplied depsim URL
|
||||
depServerURL = "http://dep.micromdm.io:9000"
|
||||
}
|
||||
client, err := dep.NewClient(conf, dep.ServerURL(depServerURL))
|
||||
|
||||
Reference in New Issue
Block a user