Contents

Using Custom MDM Payloads for FleetDM Labels

Programmatically Apply FleetDM Labels from Identity Data

For a long time now it has been considered a “bad practice,” to join a macOS computer to any sort of directory service. Long have past the days of Binding to Active Directory, and the ancient lore of the golden (or magic) triangle is nearly lost in time, like tears in the rain. The one thing that we could consider missing from these days was the ability to locally query user and identity data through native tools like dscl in macOS. I have seen very clever replacement solutions over the years to get this data down to a macOS end user devices across various enterprises, but one thing has always bothered me about every method I have seen or sometimes used. The common theme was that they were all not very security focused solutions. At a previous job, LDAP lookups were just straight up open to anyone on network, like it did not have any authentication at all. When I was in vendor space I witnessed customers having various curl solutions to grab that data from some system they could cache locally. Typically, these also were not very secure either.

The main problem being that if the service required authentication, the admin building the workflow still had to pass credentials in a script, and that script will always end up on disk in clear text at some point in time. This solution was of course better than the ones where no authentication was ever even required. So, a while ago I started playing around with custom payload variables in Jamf Pro for several years now, and immediately thought they could fill a gap we had with getting user and identity data and not wanting to do it in a non-security focused way.

So, I used this method to help me apply labels to devices in FleetDM.

The Setup

We are an Okta shop, and we have enabled a feature in Okta called Okta UD, which the TL;DR take here is that it can act like an LDAP connector for Jamf Pro. So, I worked with my CloudOps folks and Identity folks to get that setup pretty much back when I started here years ago. This is how we map User and Location data to device records in our Jamf Pro MDM.

Once setup, you can create a Directory Service EA that will map the LDAP attribute from Okta UD to your device record in Jamf Pro. Then you can craft a simple custom Configuration Profile payload like so:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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>email</key>
        <string>$EMAIL</string>
        <key>local_user</key>
        <string>$USERNAME</string>
        <key>full_name</key>
        <string>$FULLNAME</string>
        <key>position</key>
        <string>$POSITION</string>
        <key>department</key>
        <string>$DEPARTMENTNAME</string>
        <key>cost_center</key>
        <string>$EXTENSIONATTRIBUTE_108</string>
        <key>country</key>
        <string>$EXTENSIONATTRIBUTE_60</string>
        <key>city</key>
        <string>$EXTENSIONATTRIBUTE_63</string>
        <key>ldap_shortname</key>
        <string>$EXTENSIONATTRIBUTE_86</string>
    </dict>
</plist>

When looking at that XML payload you will notice $EXTENSIONATTRIBUTE_<ID> where the ID is the actual ID the EA has in Jamf Pro. You can get the ID by clicking on the object in the admin console and looking at the URL bar in your browser, you should see the integer ID that object is mapped to. So, I just filled them in according to how we have the data organized. Then when you payload the profile to disk, you can query it with simple binary tools like defaults.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
% defaults read /Library/Managed\ Preferences/com.mycustom.info.plist 
{
    city = "my office city location";
    "cost_center" = "my cost center";
    country = US;
    department = "Information Technology";
    email = "tlark@acme.com";
    "full_name" = "tlark";
    "ldap_shortname" = "my ldap shortname";
    "local_user" = "local user name";
    position = "Job Title";
}

Pretty cool, huh? We have now successfully cached some very basic identity data without having to query LDAP, without having to curl script with API credentials, or have something with no authentication on it so others can consume identity data. Also, the MDM protocol by default encrypts this traffic until it is on disk. So, zero need for AD/LDAP joining, definitely do not need a golden triangle setup, and we don’t need to really do any bad security practices here to obtain this data.

More Info:
You will need to set up some sort of directory service into your MDM, and ensure your MDM supports custom variable payloads that can leverage those LDAP (or directory) mappings.
Warning:
Also be warned that in Jamf Pro a new profile will not be pushed on data change server side. So, if a computer swaps hands to a new user you will have to have some sort of workflow account for that. In short, something like, but not limited to: touch a file, scope to an EA and use as an exclusion via Jamf Smart Groups, then once that data clears, remove the file so scope recalculates and pushes a fresh profile. If you do not like this, please file a feature request with jamf.

Setting up the Label

Assuming all is well, and the MDM is pushing down a profile to your endpoints with valid data we may proceed. If you are familiar with fleetctl you can use it to get the current labels to get an idea of how the product applies the default labels that are built-in, like so:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
% fleetctl get labels
+--------------------------------+----------+--------------------------------+---------------------------------------+
|              NAME              | PLATFORM |          DESCRIPTION           |                 QUERY                 |
+--------------------------------+----------+--------------------------------+---------------------------------------+
| All Hosts                      |          | All hosts which have enrolled  | select 1;                             |
|                                |          | in Fleet                       |                                       |
+--------------------------------+----------+--------------------------------+---------------------------------------+
| macOS                          |          | All macOS hosts                | select 1 from os_version where        |
|                                |          |                                | platform = 'darwin';                  |
+--------------------------------+----------+--------------------------------+---------------------------------------+
| Ubuntu Linux                   |          | All Ubuntu hosts               | select 1 from os_version where        |
|                                |          |                                | platform = 'ubuntu';                  |
+--------------------------------+----------+--------------------------------+---------------------------------------+
| CentOS Linux                   |          | All CentOS hosts               | select 1 from os_version where        |
|                                |          |                                | platform = 'centos' or name           |
|                                |          |                                | like '%centos%'                       |
+--------------------------------+----------+--------------------------------+---------------------------------------+
| MS Windows                     |          | All Windows hosts              | select 1 from os_version where        |
|                                |          |                                | platform = 'windows';                 |
+--------------------------------+----------+--------------------------------+---------------------------------------+
| Red Hat Linux                  |          | All Red Hat Enterprise Linux   | SELECT 1 FROM os_version WHERE        |
|                                |          | hosts                          | name LIKE '%red hat%'                 |
+--------------------------------+----------+--------------------------------+---------------------------------------+
| All Linux                      |          | All Linux distributions        | SELECT 1 FROM osquery_info            |
|                                |          |                                | WHERE build_platform LIKE             |
|                                |          |                                | '%ubuntu%' OR build_distro            |
|                                |          |                                | LIKE '%centos%';                      |
+--------------------------------+----------+--------------------------------+---------------------------------------+
| chrome                         |          | All Chrome hosts               | select 1 from os_version where        |
|                                |          |                                | platform = 'chrome';                  |
+--------------------------------+----------+--------------------------------+---------------------------------------+

Seems simple enough, just looks like some logic where if the value you are querying for exists, then return 1 and we can craft a custom label like so:

1
2
3
4
5
6
7
select
1
from plist
where path = '/Library/Managed Preferences/com.mycustom.info.plist'
and key = 'department'
and value = 'Information Technology'
;

Pro-tip, if you navigate the /labels URL in your FleetDM admin console, there is a GUI where you can drop a query in place and then name the label. I am not sure if FleetDM is going to add more UI features around this or go more into their GitOps approach. I personally really like both approaches, and I feel that if a product can pull off where everything in the UI you can also do through the API (and vice versa) that is the absolute best of both worlds.

/img/new-label-fleetdm.png

Now to finally check our work, and we can see that when we filter by label we now can use a label for the IT department!

/img/dept-label-fleetdm.png

I now see a non-zero amount of devices in FleetDM with the Information Technology Department label applied to them. You should be able to now replicate this for all departments, cost centers, and any other LDAP attribute you can map into Jamf Pro and payload to disk via custom Configuration Profile payload.