Custom DEP Packages20 Dec 2017
I’m sure everyone who didn’t have an MDM a few weeks ago is scrambling to get one set up - I’m not going to go into anything about MDM, since it really isn’t that interesting. They install profiles and packages - all very unexciting.
This article will take you through some of the decisions we made when developing our DEP enrollment package.
If you are of the open source management tool persuasion, chances are that like me, you are very happy with what you have already and see MDM merely as a method for delivering those tools. Before we considered MDM, our deployment workflow was essentially:
- Imagr lays down a base image
- Imagr installs Google’s Plan B
- Plan B install Puppet
- Puppet performs the configuration
- As part of that configuration, Puppet installs Munki
- Munki installs the software
So on the face of it, it looked pretty simple for us to use our existing Plan B package with InstallApplication via an MDM.
DEPNotify is a great tool by Joel Rennich - you can pass in various commands and it will let your users know what is going on. So we would open up DEPNotify and then kick off our Plan B installation. Which could sit there for 10 minutes without letting the user know what was going on other than “something is happening”. Whilst this obviously wasn’t a great experience for our users, it got the job done.
Rather than make our users sit there and twiddle their thumbs whilst their computer sorted it’s life out, stopped and though about what our users needed to do first off. From our perspective, we really wanted the computer encrypyed before they did anything, and we needed them to get going with our SSO solution and change their password, set up 2FA etc. So this boiled down to two basic requirements:
- Install Chrome - this is where the majority of ‘IT Time’ is spent during onboarding, so there was no need to wait for Munki to finally put it there.
- Install and configure Crypt - let’s get the disruptive logout out of the way and let the user use their computer undisturbed.
The next stage was to start letting the user know what was happening. I started going down the route of modifying our Puppet modules to support outputting text into DEPNotify’s log file, but this quickly became a pain - plus not all of our modules are written in house, so we would need to hope that the maintainer decided to merge our PR. So the next best thing was to watch the changes on disk and when certain files or directories appear on disk, we let the user know wha is happening. If I were a smarter person I would probably have used some PyObj-C framework to monitor for changes to the disk, but since we were only really concerned about a few pieces, a simple for loop sufficied. Below is an example of what we ran via a LaunchAgent. In addition to updating DEPNotify when some important files were put on disk, it also puts our default browser in the Dock, removes some apps we don’t want in there and will pop Munki in when it makes it on disk.
With Chrome and the rest of the things we wanted to install before anything else happened, our package was nudging 100MB - this left the user sitting at setup assistant with no idea that anything was happening apart from a spinning cog or even worse, at a vanilla macOS desktop with no idea what to do now.
We looked at InstallApplications by Erik Gomez, and whilst it would get us most of the way to where we wanted to be, we wanted a few other features from it (such as after we knew Crypt would be installed, we wanted to test if encryption was enabled and prompt the user to log out immediately if we needed to encrypt the disk). I did however happily steal many of it’s ideas and a lot of it’s code!
This allowed us to get our bootstrap package down to just a few scripts and a LaunchAgent and LaunchDaemon - down from 100MB-ish to just a few KB. This meant that even if the person going through Setup Assistant was very fast, they would only need to wait for DEPNotify to download before getting guided through the setup process.
Running Plan B and then running Munki afterwards was fine when we were imaging. The tech doing the imaging would kick the machine off and then go do something else whilst they waited for the machine to finish building. We couldn’t do this with a DEP style deployment - we needed to get everything completed as quickly as possible. Threads to the rescue!
By using threads, we are able to run two or more pieces of code in parallel. This meant that as soon as Munki is installed on the device by Puppet, we can kick off a run whilst Puppet continues to configure the rest of the machine.
The below snippet will wait for both Munki and it’s configuration profile to be in place, and when it is, will run
If we wanted to, we could also add in a few default pieces of optional software:
Our particular mdm doesn’t offer authentication during DEP enrollment at the moment - this doesn’t particularly bother me as there is no support for SAML or 2fa in Apple’s (awful) present implementation. We are in the process of writing something to solve this, but for now it is sufficient to ensure the device is in our inventory and is assigned to a user before enrollment continues. To solve this, we wrote a small webapp that queries inventory and returns a Boolean to the launchdaemon. If the device is unassigned the process halts and uses DEPNotify to let the user know what has happened and to contact our support folks. This is only useful to us because we are treating MDM merely as a delivery mechanism - the user is unable to proceed to get a Puppet certificate signed, so will be unable to get a correctly configured machine.
One last problem we had was enrolling existing machines into MDM - they would get this package regardless of whether their machine was fully configured or not. Our solution to this was decidedly low tech - we dropped a file in
/var/db with Puppet as the very last thing it does. We simply then exited and cleaned up immediately if the file is present - this prevented our existing machines having to sit through the (admittedly very pretty!) bootstrap process.