Categories
MacAdmin

VMware AirWatch Munki Implementation Teardown

Hi All,

As some of you may know, I am a big fan of ye old macOS management tool Munki. Though NOT an MDM, it is a powerful tool for managing macOS device apps and preferences and loved by the MacAdmins community.  Please read through on the links above if you want to know more on those…

Anyway, for those who know what all of this wonderful stuff is and are curious on how AirWatch is using the beloved tool, read below:

 

AirWatch Munki Implementation

Core Folder:

/Library/Application Support/AirWatch/Data/Munki/

Binary:

/Library/Application Support/AirWatch/Data/Munki/bin/managedsoftwareupdate

Some standard install paths exist but not used; probably created by the binary on its first run.

/Library/Managed\ Installs/Cache
/Library/Managed\ Installs/catalogs
/Library/Managed\ Installs/manifests

Contents of core folder /Library/Application Support/AirWatch/Data/Munki/:

  • Managed Installs
  • MunkiCache
  • Munki_Repo
  • bin

Main preference file:

defaults read /Library/Preferences/AirWatchManagedInstalls.plist 
{
 AppleSoftwareUpdatesOnly = 0;
 ClientIdentifier = "device_manifest.plist";
 FollowHTTPRedirects = none;
 InstallAppleSoftwareUpdates = 0;
 LastCheckDate = "2018-04-25 08:28:59 +0000";
 LastCheckResult = 0;
 LogFile = "/Library/Application Support/AirWatch/Data/Munki/Managed Installs/Logs/ManagedSoftwareUpdate.log";
 LogToSyslog = 0;
 LoggingLevel = 1;
 ManagedInstallDir = "/Library/Application Support/AirWatch/Data/Munki/Managed Installs";
 OldestUpdateDays = 0;
 PendingUpdateCount = 0;
 SoftwareRepoURL = "file:///Library/Application%20Support/AirWatch/Data/Munki/Munki_Repo/";
 UseClientCertificate = 0;
}

Compared to normal preference file location:

/Library/Preferences/ManagedInstalls.plist

Curiously, this is not a standard function to change which preference plist file it reads.

The “Munki_Repo” in the plist file above is a local folder which the binary reads as the Munki Repository (different to a tranditonal install where that would be pointing to a remote server)

The following traditional Munki Repo folders exist:

  • catalogs
  • icons
  • manifests

Traditional folders not present:

  • pkgs
  • pkgsinfo

A non traditional folder exists in the repo:

  • MunkiData

MunkiData contains a munki_data.plist which appears to be their way of knowing whats installed by them (AirWatch) and therefore knowing what to remove or not when a device un-enrolls from management. File contents:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
 <dict>
 <key>ComputedBundleID</key>
 <string>com.vmw.macos.Chrome</string>
 <key>ComputedBundleVersion</key>
 <string>66.0.3359</string>
 <key>ManagedTime</key>
 <date>2018-04-25T09:08:16Z</date>
 <key>RemoveOnUnenroll</key>
 <true/>
 <key>munki_version</key>
 <string>3.0.0.3335</string>
 <key>name</key>
 <string>Chrome</string>
 </dict>
</array>
</plist>

Here are the contents of my example manifest plist in the Munki_Repo/manifests folder:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
 <key>catalogs</key>
 <array>
 <string>device_catalog.plist</string>
 </array>
 <key>managed_installs</key>
 <array>
 <string>Chrome</string>
 </array>
</dict>
</plist>

And the example catalog file, which includes all pkginfo :

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
 <dict>
 <key>PackageCompleteURL</key>
 <string>https://localhost:7443/Application/GoogleChrome-66.0.3359.117.dmg?url=https://cdnau02.awmdm.com/cn500.airwatchportals.com/18659/Apps/1329a943-833f-4ebf-b36e-0baeb9e58d83.dmg?token=st=1524646902~exp=1524733602~acl=/*~hmac=7fb9d93e5cae26b64a3ae09553f1314fc5971a3277445b57575e366a1844149e&amp;size=68680725&amp;bundleid=com.vmw.macos.Chrome</string>
 <key>RestartAction</key>
 <string>None</string>
 <key>autoremove</key>
 <false/>
 <key>catalogs</key>
 <array>
 <string>device_catalog.plist</string>
 </array>
 <key>category</key>
 <string>Software</string>
 <key>description</key>
 <string></string>
 <key>developer</key>
 <string></string>
 <key>display_name</key>
 <string>GoogleChrome-66.0.3359.117</string>
 <key>installer_item_hash</key>
 <string>8d050591d8bd465dcae2d60a8e699bce037d0ce51f5da4349eed78b626e9ce47</string>
 <key>installer_item_location</key>
 <string>GoogleChrome-66.0.3359.117.dmg</string>
 <key>installer_item_size</key>
 <string>67071</string>
 <key>installer_type</key>
 <string>copy_from_dmg</string>
 <key>installs</key>
 <array>
 <dict>
 <key>CFBundleIdentifier</key>
 <string>com.google.Chrome</string>
 <key>CFBundleName</key>
 <string>Chrome</string>
 <key>CFBundleShortVersionString</key>
 <string>66.0.3359.117</string>
 <key>CFBundleVersion</key>
 <string>3359.117</string>
 <key>minosversion</key>
 <string>10.9.0</string>
 <key>path</key>
 <string>/Applications/Google Chrome.app</string>
 <key>type</key>
 <string>application</string>
 <key>version_comparison_key</key>
 <string>CFBundleShortVersionString</string>
 </dict>
 </array>
 <key>items_to_copy</key>
 <array>
 <dict>
 <key>destination_path</key>
 <string>/Applications</string>
 <key>source_item</key>
 <string>Google Chrome.app</string>
 </dict>
 </array>
 <key>minimum_os_version</key>
 <string>10.9.0</string>
 <key>name</key>
 <string>Chrome</string>
 <key>postinstall_script</key>
 <string>#!/bin/bash

open /Applications/Google\ Chrome.app

exit 0</string>
 <key>unattended_install</key>
 <false/>
 <key>unattended_uninstall</key>
 <false/>
 <key>uninstall_method</key>
 <string>remove_copied_items</string>
 <key>uninstallable</key>
 <true/>
 <key>version</key>
 <string>66.0.3359.117</string>
 </dict>
</array>
</plist>

The thing that stands out the most above is the PackageCompleteURL key used. Basically, the normal behaviour for items is to look in the Munki_Repo/pkgs folder for the asset but since the repo is local, they redirect to their storage for the actual package download. They do it via some local proxying method which is quite interesting…

In my example above I made the item on demand (rather than auto installed) and set a post install script to launch Chrome after it was installed (so I would know when it happened).

In a native munki world, you would be using the Managed Software Center GUI app to choose items that are “option installs” to install them on demand. In the AirWatch world, the back end system is making everything a managed install when it hits Munki, just holding it back until the user initiates it on an AirWatch portal as we’ll see shortly.

Its also worth noting that logs and other items are located in the “Managed Installs” folder as normal, except in the “/Library/Application Support/AirWatch/Data/Munki/Managed Installs/“ location rather than “/Library/Managed Installs/“

 

Walking Through Install Process

I used the “MyOrg Apps” Web shortcut the AirWatch Agent placed in my dock after it was installed and I was taken to a portal where I could browse or search for Apps that were assigned to me. On the front page was Chrome, so pressed to install and confirmed.

The AirWatch agent then started to do its work (shown. by the menu bar icon blinking) and after a minute or so Chrome launched as per my post install script.

My web based self service app portal now shows Chrome as “installed”

 

Comments On Preparation/Upload Process

The other interesting thing to note in my example is when I uploaded a DMG of the Google Chrome app into the AirWatch portal and assigned it, it asked me as part of the upload to download and install their “VMWare AirWatch Admin Assistant” on to my admin Mac to generate metadata.

The app basically asked for the DMG and less than a minute later spat out a folder in ~/Documents/VMware AirWatch Admin Assistant/ with an icon, DMG with modified name, and a plist containing the metadata the admin portal was after.

I would say in future it would be wise to run the assistant first and use the DMG it creates as I assume it makes sure the app in question is in the root level of the DMG as Munki prefers (different to the full path to place method Jamf uses for DMGs for example)

 

Final Thoughts

Overall, this is a simple but effective implementation of Munki leveraging the binary’s smarts but adding some integration with AirWatch systems to leverage the entire toolkit. It will be interesting to see how this aids AirWatch’s adoption for macOS management in the enterprise over the coming months/years.

By Aaron

“Aaron David” Polley, son of Ray Polley and Cindy L Spear, was born in Saint John, New Brunswick, Canada, October 10th, 1987. He was happily united with his wife Amie Sara Polley on August 24th, 2012, in the Sunshine Coast, Queensland, Australia.

He grew up in a musical family that had a long history of accomplished musicians and songwriters. His own writing ability surfaced at the age of 7 when his first musical arrangement was used in a church service as a congregational song.