- You cannot put credentials on a user’s device in any form that’s readable by any automation on that device and think they will not be exposed.
- You cannot send anything out over a network connection and prevent the device user from reading it.
Consider this in the case of user device apps that interact with Jamf Pro APIs. Even if you limit the API user’s permissions or don’t see the harm in the credentials being used in an attack, do not do this. Even if you don’t see the threat, the hackers will. They keep proving it to us over and over. Privilege escalations happen. If you don’t agree, please listen to the Jamf session at this year’s BlackHat or to the researchers’ follow-up visit to the Mac Admins podcast to hear them rail against this practice.
Direct connections to the Jamf Pro API are for automation scripts and apps running on secure administrator devices or process automation servers. They are not for client-side scripts or apps.
But there are tons of use cases where you need something on a user device to interact with Jamf Pro’s API. How can you do it and still stay safe?
Approach Number 1: On Macs, Let Recon Do It
Suppose we want to set an extension attribute from a user device.
Maybe you would like to ask students what grade and school they attend and write those to extension attributes or add them to a group in Jamf. That information might be very useful when scoping policies and profiles. You might be tempted to present the users with a form to ask for the information and then write it up to Jamf Pro using the API.
Or, maybe we want to set an extension attribute when something happens. We might have a launch daemon looking for some event or process and have that call a script that writes to an extension attribute value via a Jamf Pro API call. You might have a smart group that revokes some privilege on the device, sends an email notification to an administrator, triggers a policy that shows a notice to the user, etc.
There are so many inventive applications for these techniques, and they’re really good… except for the direct API call part. Jamf Pro’s ability to limit the permissions of an API user is better than that of other MDMs, most of which offer no way to limit the permissions of an API user, but even in Jamf, you can’t say, “Give this API user permissions only to update the value for this extension attribute and only on this computer”. If you give a user write access to computers, it can write anything to any computer, and you’ll probably be giving it write to users and extension attributes too since a computer payload has to interact with those areas as well. Same for mobile devices.
Get rid of that API call. For the first example, save the form data to a file and have an extension attribute script pluck the values out of that. For the event detection launch daemon, just have it call a jamf recon instead of writing to the API and replace whatever the API script was doing with an extension attribute script that reads in the same information. Yes, we have incurred the overhead of an entire recon when we could have just updated the specific extension attribute using the API. I live with it. In the world we live in, we will sometimes have to sacrifice some convenience and efficiency for security. Not always… as IT admins we evaluate trade-offs like these every day. But in this case, the sacrifice seems worth it.
Example: Infosec caught on that a user that handles customer data was running an unapproved cloud file sync utility and lost their ever-loving minds. They wanted a security incident raised immediately if someone had that running. They didn’t want to wait for a weekly recon to catch it. So, make a launchd job to look for that process name in the ps process list. If it’s there, call a recon, make an extension attribute that runs the same ps+grep as the detection or add process listings in Jamf Pro inventory prefs, create a smart group to look for that, then have that send an email on group membership change over to the infosec help desk.
Approach Number 2: Find Another Way
I recently read a blog post by another Jamf admin that presents a very clever way to figure out when a user has done an Erase all Contents and settings but did not subsequently re-enroll the device in Jamf. He installs a launchd job that looks for a file that gets created when the erase is about to happen then calls a script to do a Jamf Pro API call to set an extension attribute on the device. How can we avoid putting API credentials on the client if the computer is about to reboot and there’s not going to be time for a recon?
- Block the Reset Content and Settings feature with the restrictions profile by default.
- Put a script on a Self Service item called “Enable Reset All Content and Settings” that
- Runs a script that creates a flag file in some pre-determined path
- Runs a recon
- Create a script extension attribute called “Reset Enabled” that looks for the file created in step 2 and, if it’s there, sets the EA value to the current datetime.
- When the user clicks the Self Service button the flag file is created and the recon runs, setting the current date into the extension attribute.
- Behind the scenes, the smart group that scopes the restriction profile blocking Reset Content and Settings is set up to exclude devices that have a value in that EA value so when the recon runs and the EA value is set, the device falls out of scope for the restriction and profile is removed from the computer.
- Set a post-policy user message to say “You can now reset contents and settings…”
- Set Jamf’s enrollment options to clear extension attribute values on re-enrollment
- The user can reset all contents and settings.
- If they re-enroll, the “Reset Enabled” EA gets cleared out so the default restrictions profile gets applied.
- You can have a smart group to catch any device that has a “Reset Enabled” date greater than one day ago and put an email notification on it so you’ll know to follow up and ask the user why they did not re-enroll in management after they reset their computer.
I know, I know, that is a ridiculously convoluted workflow and not as fast since the user will need to wait for the recon to finish, which isn’t ideal. But it can all be implemented right in the Jamf Pro console — no special launchd jobs or special software installs needed. No API credentials ever go anywhere near the client. The solution is for a particular case but the general ideas have lots of applications.
Approach Number 3: Let a Proxy Do It.
Sometimes there’s just no way around it… you really do need a client-side app or process to read or write Jamf Pro data.
Maybe instead of sending an API call directly to Jamf from the user device, you could instead have the device talk to a web service and let that deal with Jamf Pro? You’d still give the app some credential to present to the web service so it’s not open to the world. For sure, that credential could be skimmed just like actual Jamf Pro API credentials, but there’s a big advantage… you control the API code. That means you can ensure that the API credentials will only be used for the limited purposes you intend. For example, a web service could expose an endpoint used to update the value of a single extension attribute even though the user it uses to talk to Jamf Pro has the rights to update any extension attribute or computer value.
The web service gets the HTTP message from the client and only the web service has the API credentials and does the Jamf API calls. You could host the applet yourself if you have access to a web server, but these are increasingly done in the cloud with something like an AWS Lambda or Google functions. Azure has a bunch of options for doing this kind of thing. They are super cheap, have all the added security that these services offer, and there are options to secure the API credentials in things like AWS Configuration Manager. There are also dozens of cloud services that are purpose-built for creating these kinds of process flows (Zapier, etc.). Some of them are ridiculously easy, like to the point that you can create something really complex with drag-and-drop programming.
In the case discussed in approach 2, instead of having the client-side script send a curl to the jamf api, you could instead hit a “/set_ea/eacs_done/<computer_id>” endpoint and have the web app pick off the computer id and write the current date to the device’s Jamf Pro record.
Of course if you are using a commercial cloud service, this approach is trading one risk for another. We are no longer putting API credentials on the client Macs, but we are giving them to a third party. Make sure the provider you choose has security and privacy certifications consistent with your organizations compliance standards.
Approach Number 4: Same as above, but with per-device Authentication
In the “update values of a single extension attribute” example we’ve been using, a web app might is just setting an informational EA with nothing scoped to it and not used in any other way. So there’d be little point in an attacker trying to mess with it. You might be happy to settle for giving clients a shared credential to use when hitting the web service so you won’t get stuck paying the bill for blind DDOS attacks.
But what if you need the device to prove that it’s really the device it says it is. There are lots of ways, some simpler than others. For user-interactive flows, you can put up some GUI or a web page and make the user authenticate. You could install a cert on devices that it presents to a web service when negotiating mTLS. Or, you can run a script that:
- Generates a signing certificate
- Generates some random padding bytes
- Loops through every device in Jamf Pro
- Concatenates the device’s id with the padding
- Calculates a signature for those bytes using the signing cert and hashes the result
- Writes that hash to an extension attribute on the device record or saves it on the device, like in a keychain
Then you can use that EA as a value in any Jamf Pro script where the device is going to have to prove itself to something. Or you can include the EA value in an app config for an iOS app.
Then you give that same signing certificate and padding to your web app. When it gets “/set_ea/eacs_done/<computer_id>/<my_signature_ea>”, it uses the cert and padding to figure out what the signature should be if the the device really were <computer_id>. If that matches the signature presented by the client, the web app processes the request. Otherwise, it discards it.
Yes, an attacker can obtain this signature value off of a device. But as long as the signing certificate is kept safe, they’re going to have a very hard time figuring out the signature for any other client so they’re not going to have a way to mess with the data of any other device record.