Git Product home page Git Product logo

micromdm's Introduction

MicroMDM - a devops friendly MDM server

CI

MicroMDM is a Mobile Device Management server for Apple Devices, focused on giving you all the power through an API.

User Guide

  • Introduction
    Requirements and other information you should know before using MicroMDM.

  • Quickstart
    A quick guide to get MicroMDM up and running.

  • Enrolling Devices
    Describes customizing the enrollment profile and the options available to get the profile installed on a device. Covers DEP provisioning as well as manual profile installs.

  • API and Webhooks
    High level overview of the API used for scheduling device actions and processing the responses.

Developer Guide

To help with development, start by reading the CONTRIBUTING document, which has relevant resources.

For a local development environment, or a demo setup, the ngrok guide, is the best resource to get something working.

micromdm's People

Contributors

bdemetris avatar bpmcneilly avatar choehn-signogy avatar clburlison avatar daemonsy avatar dependabot[bot] avatar discentem avatar grahamgilbert avatar groob avatar hrgbcxd avatar jenjac avatar jessepeterson avatar knightsc avatar korylprince avatar larsar avatar loyaltyarm avatar marpaia avatar meta-github avatar mobiledan avatar mosen avatar n8felton avatar natewalck avatar petitout avatar slawoslawo avatar thepriefy avatar tomaswallentinus avatar tperfitt avatar tricknotes avatar wardsparadox avatar williamtheaker avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

micromdm's Issues

Issue unique SCEP challenges per device.

Right now the embedded SCEP server does not issue any challenge passwords for SCEP.
MicroMDM should create (and keep track of) a unique password for each enrollment and only allow a single use.

Alternatively create a short lived token which would expire in <1 minute( but this would cause issues with enrollment profiles which need to be emailed to a user).

Marshal remaining Device fields

the device.proto file is missing a few fields like LastCheckin.
These should be added to the proto file, then run go generate then add them to the device Marhsal/Unmarshal methods in device/device.go.

add dep sync in addition to fetch

The binary starts up and starts fetching devices from DEP.
We should rely on sync after the initial fetch is done, but for that to happen, we need to persist the cursor and other configuration so we in the same place where we left off after a restart.

First solve: #87 which will require adding a dep configuration bucket.

Add example syntax to -h command

When using sudo micromdm serve -h, there is no example syntax given. This would be helpful to see outside of the specific example in the quickstart, simply because some commands use a = sign and some do not. Feel free to close if too trivial, but an example would be where a particular user is using .pem and not .p12.

arg -filerepo accepts non-existent paths

If you specify an invalid path for -filerepo, there is no indication it does not exist or you lack access to the path:

sudo $GOPATH/bin/micromdm serve -server-url=https://server.domain.com -apns-cert=/path/to/pushcert.p12 -apns-password="PASSWORD" -filerepo /foo/bar

It should exit saying the path is invalid rather than running as if it had worked.

concurrent map read/write in pubsub

=== RUN   TestPubSub
==================
WARNING: DATA RACE
Write at 0x00c4200788d0 by goroutine 6:
  runtime.mapassign()
      /usr/local/go/src/runtime/hashmap.go:485 +0x0
  github.com/micromdm/micromdm/pubsub.(*Inmem).Subscribe()
      /Users/victor/go/src/github.com/micromdm/micromdm/pubsub/consumer.go:15 +0x26a
  github.com/micromdm/micromdm/pubsub.TestPubSub()
      /Users/victor/go/src/github.com/micromdm/micromdm/pubsub/inmem_test.go:21 +0x33a
  testing.tRunner()
      /usr/local/go/src/testing/testing.go:657 +0x107

Previous read at 0x00c4200788d0 by goroutine 7:
  runtime.mapaccess1_faststr()
      /usr/local/go/src/runtime/hashmap_fast.go:208 +0x0
  github.com/micromdm/micromdm/pubsub.(*Inmem).dispatch()
      /Users/victor/go/src/github.com/micromdm/micromdm/pubsub/consumer.go:25 +0x14d

Goroutine 6 (running) created at:
  testing.(*T).Run()
      /usr/local/go/src/testing/testing.go:697 +0x543
  testing.runTests.func1()
      /usr/local/go/src/testing/testing.go:882 +0xaa
  testing.tRunner()
      /usr/local/go/src/testing/testing.go:657 +0x107
  testing.runTests()
      /usr/local/go/src/testing/testing.go:888 +0x4e0
  testing.(*M).Run()
      /usr/local/go/src/testing/testing.go:822 +0x1c3
  main.main()
      github.com/micromdm/micromdm/pubsub/_test/_testmain.go:42 +0x20f

Goroutine 7 (running) created at:
  github.com/micromdm/micromdm/pubsub.NewInmemPubsub()
      /Users/victor/go/src/github.com/micromdm/micromdm/pubsub/publisher.go:21 +0x15e
  github.com/micromdm/micromdm/pubsub.TestPubSub()
      /Users/victor/go/src/github.com/micromdm/micromdm/pubsub/inmem_test.go:10 +0x47
  testing.tRunner()
      /usr/local/go/src/testing/testing.go:657 +0x107
==================
--- FAIL: TestPubSub (0.01s)
	testing.go:610: race detected during execution of test
FAIL
exit status 1
FAIL	github.com/micromdm/micromdm/pubsub	0.041s

improve handling of DeviceConfigured

right now we just fire off DeviceConfigured in an array of commands; if the device replies with NotNow, we might want to hold off on sending device configured, or not. up to the admin.

micromdm should handle the following 2 modes:
a) Fail to pass setup assistant unless an explicit list of commands are Acknowledged.
b) Try the initial list of commands as a best effort, send DeviceConfigured anyway. (current mode).

Add documentation for using self-signed TLS certificates for testing (or science)

If you follow the quickstart, it is assumed you are on the public internet ๐Ÿ˜ฑ. We should have some documentation about using self-signed TLS certs so that users/testers can use https://localhost to run their MDM server if needed for testing (or science). Without this, you can start the micromdm server on localhost, but will receive the following errors when trying to hit /mdm/enroll:

component=main msg=started
no DEP server configured. skipping device sync from DEP.
no blueprint specified in /etc/micromdm/blueprint.json, skipping default device actions
transport=HTTP addr=:https
ts="2017/03/21 17:08:00" caller="http: TLS handshake error from [::1" msg="]:53508: 400 urn:acme:error:malformed: DNS name does not have enough labels"
ts="2017/03/21 17:08:00" caller="http: TLS handshake error from [::1" msg="]:53509: acme/autocert: missing certificate"

Feature/Workflow Request - a well-defined resolver like in MCX

Apologies in advance if this is not the appropriate place to post this...

One thing I'd really like to see in an MDM solution is a well-defined resolver, like we had with MCX and Workgroup Manager. It sounds like workflows could be the solution for this.

With Workgroup Manager and MCX, you could apply settings at the User, Group, Machine or Machine Group level and always know that a setting applied for the User would override settings at the Group level which in turn would take precedence over Machine settings which take precedence over Machine Group settings.

There is none of this baked natively into profiles and applying conflicting settings in different profiles leads to undefined behaviour.

With a profile-based workflow, it seems that the heavy lifting needs to be done by the MDM server, resolving conflicts itself and then generating a single profile for each machine and each user that has all the conflicting settings resolved already.

Then, on each individual machine, there will be the minimum number of profiles - the enrolment profile, a machine specific profile and an individual user profile.

Any changes to the settings in these profiles would result in the entire profile being pushed out all over again, however there (hopefully) wouldn't be any conflicts in the settings across the installed profiles as the MDM server has already done all the hard work in working out which settings take precedence and pushing out just those settings.

This would change the workflow somewhat from the administrator creating a set of profiles with each profile containing a single payload and then pushing out a combination of these profiles to the administrator configuring the required settings, the MDM server resolving them into one or two profiles with multiple payloads and these profiles being pushed out.

It does increase complexity at the server end however it leads to a simpler experience at the client end and less confusion over which setting will apply when conflicting settings are applied at different levels.

properly handle the queue

I punted on this over the weekend and just deleting the command as soon as the device requests it.

We need to handle all there responses correctly. This was implemented with redis in the past so should be a easy change.

But I'd like to look at some of the ideas @mosen added in his fork as well.

move all blank( _ ) imports to package main

Several of the imports, like the postgres driver are imported directly into the package.
This should never be done as it prevents the package from being used with another driver.
See here for an example of such an import.

All imports starting with _ need to be removed from individual packages and added to package main instead.

See Effective Go for explanation.

Help on invalid enrollment mobileconfig

In my test, the service is run as:

sudo ~/Downloads/micromdm/build/micromdm-darwin-amd64 \
    -tls \
    -tls-cert=~/1/cert1.pem \
    -tls-ca-cert=~/1/chain1.pem \
    -tls-key=~/1/privkey1.pem \
    -postgres='user=user password=micromdm dbname=micromdm sslmode=disable' \
    -redis='127.0.0.1:6379'  \
    -enrollment-profile=~/Enroll.mobileconfig \
    -push-cert=~/Documents/APSP_2.p12 \
    -push-password='somepassword' \
    -http-address='127.0.0.1:9000' \
    -url='https://my-mdm-service-url:9443/'

But I got error messages from both Safari or Mail on my phone, and on macOS I got complains about insufficient IdentityCertificateUUID in the payload com.github.micromdm.mdm.

Scrutinizing the mobileconfig downloaded from '/mdm/enroll', I confirmed that the value to IdentityCertificateUUID was empty.

BTW, the Enroll.mobileconfig was tested ok using Email and Apple Configurator 2.

provide an automated way of generating appmanifeststs

We already have the appmanifest utility from the tools repo, but we can do better here.
Here's an idea for a possible user experience:

micromdm import-pkg /path/to/file.pkg
which would:
a) validate that the package is signed (we could also look into automated signing but that's extra tools)
b) validate it's a distribution package. - go-xar can help.
c) generate an appmanifest and copy the package and manifest automatically to be served by the built in fileserver.

app: make passing an enrollment profile optional

I have this labeled as a TODO. It would be great to remove the requirement for a pre-built enrollment profile.

My suggestion would be to introduce a functional option, like I did in the driver package to allow passing optional arguments during setup.

type ConnOption func(c *config)

micromdm/app/services.go

Lines 230 to 234 in fe50027

opts := []driver.ConnOption{driver.Logger(s.logger)}
if s.Redis.Password != "" {
opts = append(opts, driver.WithPassword(s.Redis.Password))
}
s.pool, s.err = driver.NewRedisPool(s.Redis.Connection, opts...)

See this talk for a reference. Also as video](https://www.youtube.com/watch?v=24lFtGHWxAQ).

add list/describe/get command to mange devices

Need to implement the following commands:

  • micromdm list devices which will print out a list of devices with a format like below and some flags to filter devices.
UDID     SerialNumber    EnrollmentStatus    LastSeen
  • micromdm describe devices -serial=SERIAL
    Which will print a detailed view of a device.

And finally:

  • micromdm get devices -serial=SERIAL -o=json and get a JSON output of the device.

Error processing device response to Query

My fault. Noted here for posterity:

retrieving device by UDID: sql: Scan error on column index 1: unsupported Scan, storing driver.Value type <nil> into type *string

Will fix in hotfix branch.

help the user with anchor certs if the server certificate is self signed

from the MDM reference:

Optional. Array of strings. Each string should contain a DER-encoded certificate converted to Base64 encoding. If provided, these certificates are used as trusted anchor certificates when evaluating the trust of the connection to the MDM server URL. Otherwise, the built-in root certificates are used.

most users work with PEM certificates, so it would be great to provide some sort of helper for anchor certificates. Maybe the template for the DEP profile can auto-set them?

Migrator was missing after refactoring main.go

In commit 2751a79, main.go got refactored for #49.

However, some code deleted was not added back to other sources. In particular, the migrator calls have been missing since the this commit. Without these calls, the pg migration SQLs would not be executed, leading to an empty DB after fresh installs, which in return renders the server unusable.

Feature: Payload Composition

People have asked about creating profiles using a model similar to OOP class inheritance.
I want to discuss another idea which is to create and deliver profiles via payload composition.

Payloads vs Profiles

As you know, profiles are the "container" for one or many payloads. They represent a chunk of configuration. Payloads on the other hand cover a single topic of configuration, which seems to me like a great place to draw a line when you are attempting to slice this problem into a lower level abstraction.

From what I can see, there are two approaches to configuration management via profiles. More commonly people will use a single payload per profile to deal with that piece of configuration individually. If you're using Profile Manager (why?) you might be dealing with a whole chunk of configuration for a single user or device.

The first use case really highlights the fact that most of the time you are using profiles as a wrapper for the most atomic level of configuration available, which is a payload. But you might recognise that there are problems with composition if you have two of the same payload.

I propose that we deal exclusively with payloads as the primary unit of config within the system, and there are a few problems that come along with this approach. I think that the problem of creating a profile inheritance hierarchy can be solved via composition instead.

Payload Editing and Composition

Ok, so the payload is the basic unit of configuration. This simplifies UI layouts for payload editing because you only need to focus on a single PayloadType for each "editor" UI.

The composition comes in when it is time to generate a profile. First of all you have the flexibility to define many payloads outside of a profile. This means that you can re-use an element of configuration (payload) in many different profiles.

This also means that you can change details within a single payload and have all dependent profiles updated to include the new information.

The end user would generate a payload for each item of configuration they wanted to manage (maybe several payloads of the same type if they differ for different departments). The system would then provide a way of generating a profile based upon a given collection of payload UUIDs.

This model could be extended to support a kind of inheritance via composition if groups of payloads could include nested groups of payload within themselves.

In this way you can abstract out the common payload elements and then create new compositions that articulate settings that are catered to a specific group.

Problems

Problems occur with this abstraction when functionality depends on the top-level Profile keys.
Largely this isn't a problem except for PayloadIdentifier.

PayloadIdentifier is the single field responsible for identifying whether a profile should be entirely replaced, or a new one added.

There are two approaches to solving this issue:

  1. Every composition of payloads is assigned an identifier for its resulting profile OR
  2. The PayloadIdentifier field is generated based upon the composition of payloads within the profile. One algorithm might be joining all of the UUIDs present in the profile, sorted lexicographically.

The advantage of the second approach is that the composition of two profiles with the same content results in the same identifier, so it isn't necessary to store a one-many or many-many relationship of profile IDs to payloads.

I'm still not absolutely sure about the functionality of PayloadIdentifier within individual payloads and whether an identical value there causes the domain plugin to skip over the payload. I would propose that a hash of the payload content be used to generate each payload identifier so that any change in the content is reflected if that's the case.

Tidbits

It could be possible to provide an endpoint that generates a profile based on payload UUIDs like so:

https://micromdm.dev/payloads/profile/{uuid1},{uuid2},{uuid3}.

This could be handy for downloading composed profiles on the fly, and the profile identifier is guaranteed to be the same which means the profile is replaced each time you download and install it.

That's all for now! I hope that generates some ideas or questions in any case.
m

Allow per-device blueprints

The /etc/micromdm/blueprint.json config is a bit of a hack right now and is global to every enrollment.

Work out a way to do a per device config(and later per group).
An acceptable place to start would be to check for a serial_number.json file and use that, similar to munki manifests.

Even better if the blueprints can be nested too.

move dep-token command to a library.

right now it's all in package main, making it difficult to reuse. all dep related configuration and commands should be in their own package, imported by main.

app: allow passing separate APNS certificate and private key.

Currently the flags menu reads:

  -push-cert string
        path to push certificate
  -push-key string
        path to push certificate private key(if not using a single .p12 file)
  -push-password string
        push certificate password

but only the .p12 file is actually supported.

It would be easy to load the certificate and key separately.
The setupPushService uses a helper to unbundle the p12 file, but then the push.NewClient expects the unbundled x509.Certificate + rsa.PrivateKey.
Check to see if the config is not empty and load the cert and key from the given paths.

For reference:
https://golang.org/pkg/crypto/x509/#ParseCertificate
https://golang.org/pkg/crypto/x509/#ParsePKCS1PrivateKey
micromdm/certhelper has a bunch of loading/parsing code for keys.

It would be perfectly acceptable to only support loading PEM formatted certs/keys. That's the format that you get the cert in from identity.apple.com and from certhelper.

add a simple homepage

potentially redirect base url (/) to /mdm/enroll endpoint for forgetful people like me who just hit the base URL during testing ๐Ÿค”

Build Error

I've executed the command
go get github.com/micromdm/micromdm
but I get this error:

# github.com/micromdm/micromdm/device
go/src/github.com/micromdm/micromdm/device/device.go:110: cannot use &dd.ProfileAssignTime (type *time.Time) as type time.Time in field value
go/src/github.com/micromdm/micromdm/device/device.go:111: cannot use &dd.ProfilePushTime (type *time.Time) as type time.Time in field value
go/src/github.com/micromdm/micromdm/device/device.go:112: cannot use &dd.DeviceAssignedDate (type *time.Time) as type time.Time in field value

checkin: EnrollDEP should be moved from checkin to enroll service.

We now have a proper enroll package, and it would make sense to manage DEP enrollment there, while keeping checking as a separate service.

The DEP enrollment process needs a lot of other work/better tie-in with Workflows, but just moving it would be a good start.

Alternatively, It could be moved to it's own package as it will need to depend on several other services. Keeping it separate, removes coupling between different components to a minimum.

Postgres migrations should be added to binary.

Currently the app will fail to start if the migrations folder is not in the same folder as the binary.
We can make this better by bundling the migration files with the binary.

Solution 1: Add the migration files with bindata.
Either commit the bindata.go file to the repo, or figure out a way to bundle it during release.

Solution 3: Look if the migration library allows writing migrations as .go files.

persist configuration passed by flags.

Right now micromdm has a bunch of flags passed at startup, like -server-url or -filerepo
These should be persisted in a configuration bucket (and updated when the daemon is restarted in case they change).

Persisting the configuration of the running daemon will allow the other subcommands to take advantage of the config. For example the -import-pkg mentioned in #93 could use the server-url.

Document `/v1/commands` API

We have a pretty good API that nobody knows about because it's not documented. This is a TODO to document examples of commands using curl.

Verify the device identity certificate

We want to verify the device identity certificate which was signed by the scep CA.
This cert is only necessary for the /mdm/connect endpoint. Unfortunately cert auth happens during a TLS handshake so we can't verify it in the http handler.

Here are the options for the connect endpoint:
Bind to :8443 (configurable by the user) and require the identity certificate there.

or:

use tls.Config.GetConfigForClient func(*ClientHelloInfo) (*Config, error) and require certificate for
connect.mdm.acme.co

Either way the user has to do additional configuration.

provide consistent logging throughout the app

Right now we fmt.Pritnf or log.Printf where convenient, but that's not a sustainable strategy longterm.

How logging should be implemented:
Use the go-kit log package, it's great
https://github.com/go-kit/kit/tree/master/log

the log should provide a separate context for each component and two logging levels configurable by the user: debug(a firehose of data useful for debugging/development) and info which will be the default logging mode.

Labelling this as beginner-friendly. We'll have to think holistically about logging through the app, but pretty much anyone is welcome to take any .go file and improve logging by switching the fmt/stdlib logging to a go kit logger. :)

Support AccountConfiguration in blueprint

Right now the blueprint takes a list of profiles and a list of applications. But during DEP I want to also provide some account configuration with an admin user.

Two things need to happen:

  • Update protobuf encoding for AccountConfiguration. See here
  • Implement password hashing: The SaltedSHA512PBKDF2 method here should help.

configurable authentication for enrollment endpoint

The enrollment URL is accessible through two separate methods:

DEP devices send a POST:

  • because of restrictions with DEP we can only issue a 401 withWWW Authenticate here.
    Support a basic auth here for now.

Users send a GET to /mdm/enroll. We have more options here but it's also way too possible configurations. Let's do two for now(and implement others as requested).

  • basic auth - same as DEP
  • google with Oauth2.

This works for me and will likely be common enough for a large number of users so it's a good start.

all: change how `package.NewDB()` functions are defined

Right now there' are several packages which have functions like NewDB(driver, conn string) and return some configured Postgres backed datastore. I created a new package, driverwhich has a reusableNewSQLxDB(...) function.

Change the function arguments in these packages to instead accept a *sqlx.DB as an argument, and pass an already created *sqlx.DB from the setup function, using the driver package. Remove un-necessary code.

Doc: https://godoc.org/github.com/micromdm/micromdm/driver

checkout previously known devices after a server reset

I end up resetting the datastore a lot during development and I'm noticing some weird behavior around that, especially if there are still devices out there that have a valid enrollment profile.

The default behavior of an unknown UDID trying to /connect should be to return a 401, which would cause the device to un-enroll.
This is also security 101.

However, because the results of an unauthorized error are so drastic (immediate removal of the enrollment profile), we should create a custom error like UnauthroizedDeviceRequest and test it properly.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.