Jamf Pro 11.12 API testing page now accepts both password authentication and API client authentication

As part of supporting Jamf Pro‘s API functionality, Jamf has made interactive documentation pages available with every Jamf Pro installation via the following address:

https://jamf.pro.server.here/api

When choosing to view the Classic API or Jamf Pro API documentation, there’s an option to log in and authenticate, so that you can run API commands interactively to see how running the commands works in real time and what results you see. This functionality is also useful when setting up API accounts with least privileged access, as it allows testing to verify that all necessary privileges have been assigned to the accounts.

Up until now, this authentication mechanism only supported using username and password authentication but as of Jamf Pro 11.2.0, the authentication mechanism now supports both of the following authentication methods:

  • Username/Password authentication
  • API client authentication

To use your preferred authentication method, please select it from the relevant drop-down menu.

Management profile settings and OS upgrade implications

A question I’ve seen repeatedly in the Mac Admins Slack goes something like this:

“I installed this profile for macOS NewVersion onto macOS OldVersion, then upgraded from macOS OldVersion to macOS NewVersion. The setting didn’t work. Why didn’t it work?”

Why it didn’t work has to do with how management profile settings are handled. When a management profile is installed, the settings contained within that profile are applied.

This settings application occurs exclusively at the time of the profile installation. Those applied settings are never again re-read or re-applied as long as that profile is installed. The settings in a profile are applied only at the time of installation and that is the current state of things.

How is this relevant to settings you want to apply to macOS? Apple defines what OS version a setting was introduced for, which means it does not work for OS versions prior to that. For more information, please see below the jump.

An example of this is the management setting for iPhone mirroring:

https://github.com/apple/device-management/blob/1fa842739c8f19db5b62f3ac6aed261cc378e5b8/mdm/profiles/com.apple.applicationaccess.yaml#L1834-L1860

This setting was introduced for macOS as of macOS 15 Sequoia. That means that the setting works on macOS Sequoia but what happens when you install a management profile like the one below which contains this setting onto a Mac running macOS 14 Sonoma?

Nothing.


<?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"&gt;
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadDisplayName</key>
<string>Restrictions</string>
<key>PayloadIdentifier</key>
<string>com.apple.applicationaccess.DD8454BB-A1D6-4DD9-B1AC-C1B6ABA512E9</string>
<key>PayloadType</key>
<string>com.apple.applicationaccess</string>
<key>PayloadUUID</key>
<string>DD8454BB-A1D6-4DD9-B1AC-C1B6ABA512E9</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>allowiPhoneMirroring</key>
<false/>
</dict>
</array>
<key>PayloadDescription</key>
<string>Blocks the use of iPhone mirroring</string>
<key>PayloadDisplayName</key>
<string>Block iPhone Mirroring</string>
<key>PayloadIdentifier</key>
<string>D0B2E096-2C7F-4F2F-A1C0-FFE16919768B</string>
<key>PayloadOrganization</key>
<string>Company Name</string>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>D0B2E096-2C7F-4F2F-A1C0-FFE16919768B</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>

A profile’s settings get applied at the time of installation. If the setting isn’t understood by the OS the profile is installed onto at the time of installation, the setting is ignored.

In the context of the management setting for iPhone mirroring, macOS Sonoma doesn’t have the management option for managing iPhone mirroring so Sonoma will ignore the setting. It will remain ignored if the Mac gets upgraded to Sequoia because the setting only gets applied at the time of installation and the setting never gets re-evaluated to see if it applies to Sequoia. The outcome is that the setting does not get applied on Sequoia if the profile with the setting was installed on Sonoma.

How do you fix this? Remove the profile with the iPhone mirroring setting from the Sequoia Mac and re-install the profile. Once the profile is installed again, the setting will get applied as part of the install process. Sequoia has that setting as a management option, so Sequoia will then apply the setting from the profile and manage iPhone mirroring as defined by the profile’s settings.

So what does this mean for management settings you want to apply to macOS NewVersion? You’ll need to check what the introduction version is for the setting you want to apply. If it’s a brand new setting where the introduction is on macOS NewVersion, you’ll need to wait until the Mac is running macOS NewVersion before deploying a profile to manage that setting.

For Mac admins who want the capability to install a setting on macOS OldVersion and have it apply to macOS NewVersion, I recommend filing feedback with Apple to request it .

Suppressing the Apple Intelligence pop-up window with a configuration profile on macOS Sequoia

Apple has introduced a number of pop-up windows over the years, which appear the first time you log into a Mac and sometimes also after an OS update. Apple added a new one for macOS Sequoia as part of introducing Apple Intelligence.

The Apple Intelligence pop-up window can be suppressed for the logged-in user by running the command shown below:


/usr/bin/defaults write com.apple.SetupAssistant DidSeeIntelligence -bool true

view raw

gistfile1.txt

hosted with ❤ by GitHub

 

It is also possible to suppress the Apple Intelligence pop up window on macOS Sequoia using a configuration profile. For more details, please see below the jump.

The relevant preference domain and key values are below:

  • Preference domain: com.apple.SetupAssistant.managed
  • Key: SkipSetupItems
  • Value: Intelligence

The profile is available on GitHub via the link below:

https://github.com/rtrouton/profiles/blob/main/SkipAppleIntelligenceSetup

Managing Apple Intelligence features on macOS Sequoia 15.2

As a follow-up to my earlier post on managing Apple Intelligence features on macOS Sequoia, Apple has added a couple of new management options for Apple Intelligence now that Apple Intelligence is able to communicate with external services like ChatGPT. For more details, please see below the jump.

As of macOS 15.2, management options are available for the following Apple Intelligence functionality:

The relevant key values are below:



Restriction Setting available in version Description Key Key value Default setting in macOS
Allow Image Playground macOS 15.0.0 If key vaule is set to FALSE, prohibits the use of image generation. allowImagePlayground Boolean TRUE
Allow Writing Tools macOS 15.0.0 If key vaule is set to FALSE, allows only anonymous access to external services allowWritingTools Boolean TRUE
Allow Genmoji macOS 15.0.0 If key vaule is set to FALSE, disables Genmoji allowGenmoji Boolean TRUE
Allow Mail Summary macOS 15.1.0 If key vaule is set to FALSE, prohibits the ability to create email summaries allowMailSummary Boolean TRUE
Allow External Intelligence Integrations macOS 15.2.0 If key vaule is set to FALSE, prohibits integrations with external services including ChatGPT and Google Gemini allowExternalIntelligenceIntegrations Boolean TRUE
Allow External Intelligence Sign-Ins macOS 15.2.0 If key vaule is set to FALSE, disables non-anonymous login to external services including ChatGPT and Google Gemini allowExternalIntelligenceIntegrationsSignIn Boolean TRUE

It’s important to note that while all of the settings listed above work on macOS Sequoia 15.2, not all work on earlier versions of macOS Sequoia. Here’s the compatibility list:

macOS 15.0 and later:

  • allowGenmoji
  • allowImagePlayground
  • allowWritingTools

macOS 15.1 and later:

  • allowMailSummary

macOS 15.2 and later:

  • allowExternalIntelligenceIntegrations
  • allowExternalIntelligenceIntegrationsSignIn

These settings can be managed by a configuration profile, where setting a boolean value of false will disable the Apple Intelligence feature in question. Please see below for example profiles. The example profiles are also available via the following links:

Note: If you’re planning to use the example profiles with Jamf Pro, the profiles will need to be signed before they can be uploaded to Jamf Pro. If you’re not familiar with how to sign profiles, the post linked below is a good guide to how that process works:

https://macblog.org/sign-configuration-profiles/

Genmoji:


<?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"&gt;
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadDisplayName</key>
<string>Restrictions</string>
<key>PayloadIdentifier</key>
<string>com.apple.applicationaccess.1281701E-9695-4447-9028-4962C25162FF</string>
<key>PayloadType</key>
<string>com.apple.applicationaccess</string>
<key>PayloadUUID</key>
<string>1281701E-9695-4447-9028-4962C25162FF</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>allowGenmoji</key>
<false/>
</dict>
</array>
<key>PayloadDescription</key>
<string>Disables creation of new Genmoji</string>
<key>PayloadDisplayName</key>
<string>Apple Intelligence Disable Genmoji</string>
<key>PayloadIdentifier</key>
<string>B83678F5-B2CB-467C-A89F-73F2E2E1346C</string>
<key>PayloadOrganization</key>
<string>Company Name</string>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>B83678F5-B2CB-467C-A89F-73F2E2E1346C</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>

Image Playground:


<?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"&gt;
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadDisplayName</key>
<string>Restrictions</string>
<key>PayloadIdentifier</key>
<string>com.apple.applicationaccess.4FDE23F1-2652-4653-813C-205C9B86C0F5</string>
<key>PayloadType</key>
<string>com.apple.applicationaccess</string>
<key>PayloadUUID</key>
<string>4FDE23F1-2652-4653-813C-205C9B86C0F5</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>allowImagePlayground</key>
<false/>
</dict>
</array>
<key>PayloadDescription</key>
<string>Disables Image Playground and prohibits the use of image generation</string>
<key>PayloadDisplayName</key>
<string>Apple Intelligence Disable Image Playground</string>
<key>PayloadIdentifier</key>
<string>5596EE02-5B47-4B4C-B3F0-AA531C1E9AEB</string>
<key>PayloadOrganization</key>
<string>Company Name</string>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>5596EE02-5B47-4B4C-B3F0-AA531C1E9AEB</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>

Writing Tools:


<?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"&gt;
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadDisplayName</key>
<string>Restrictions</string>
<key>PayloadIdentifier</key>
<string>com.apple.applicationaccess.2C74FDD6-E3CD-4E3B-9193-CD4818452895</string>
<key>PayloadType</key>
<string>com.apple.applicationaccess</string>
<key>PayloadUUID</key>
<string>2C74FDD6-E3CD-4E3B-9193-CD4818452895</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>allowWritingTools</key>
<false/>
</dict>
</array>
<key>PayloadDescription</key>
<string>Disables Apple Intelligence writing tools</string>
<key>PayloadDisplayName</key>
<string>Apple Intelligence Disable Writing Tools</string>
<key>PayloadIdentifier</key>
<string>FDDB4857-545D-4538-9C0B-B8ED78FFCE3E</string>
<key>PayloadOrganization</key>
<string>Company Name</string>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>FDDB4857-545D-4538-9C0B-B8ED78FFCE3E</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>

Summarize emails:


<?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"&gt;
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadDisplayName</key>
<string>Restrictions</string>
<key>PayloadIdentifier</key>
<string>com.apple.applicationaccess.6DD01B26-8368-45FE-A4F7-35F4CD153E5D</string>
<key>PayloadType</key>
<string>com.apple.applicationaccess</string>
<key>PayloadUUID</key>
<string>6DD01B26-8368-45FE-A4F7-35F4CD153E5D</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>allowMailSummary</key>
<false/>
</dict>
</array>
<key>PayloadDescription</key>
<string>Disables Mail Summary and prohibits the ability to create email summaries</string>
<key>PayloadDisplayName</key>
<string>Apple Intelligence Disable Mail Summary</string>
<key>PayloadIdentifier</key>
<string>45B76C44-A61D-4A1B-82B9-6118B18DB129</string>
<key>PayloadOrganization</key>
<string>Company Name</string>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>45B76C44-A61D-4A1B-82B9-6118B18DB129</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>

Block Siri from connecting to third party cloud-based intelligence services:


<?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"&gt;
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadDisplayName</key>
<string>Restrictions</string>
<key>PayloadIdentifier</key>
<string>com.apple.applicationaccess.69140388-BF31-4C0E-A791-F8EFDCB54C49</string>
<key>PayloadType</key>
<string>com.apple.applicationaccess</string>
<key>PayloadUUID</key>
<string>69140388-BF31-4C0E-A791-F8EFDCB54C49</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>allowExternalIntelligenceIntegrations</key>
<false/>
</dict>
</array>
<key>PayloadDescription</key>
<string>Disables External Intelligence Integrations and prohibits integrations with external services including ChatGPT and Google Gemini</string>
<key>PayloadDisplayName</key>
<string>Apple Intelligence Disable External Intelligence Integrations</string>
<key>PayloadIdentifier</key>
<string>2B3EE9B6-249E-44DD-B9A6-1E71F72A7E34</string>
<key>PayloadOrganization</key>
<string>Company Name</string>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>2B3EE9B6-249E-44DD-B9A6-1E71F72A7E34</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>

Disable non-anonymous login to third party cloud-based intelligence services:


<?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"&gt;
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadDisplayName</key>
<string>Restrictions</string>
<key>PayloadIdentifier</key>
<string>com.apple.applicationaccess.EB34F905-0ED5-4E29-9A4A-5AE77F4D6652</string>
<key>PayloadType</key>
<string>com.apple.applicationaccess</string>
<key>PayloadUUID</key>
<string>EB34F905-0ED5-4E29-9A4A-5AE77F4D6652</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>allowExternalIntelligenceIntegrationsSignIn</key>
<false/>
</dict>
</array>
<key>PayloadDescription</key>
<string>Disables External Intelligence Sign-in and allows only anonymous access to external services</string>
<key>PayloadDisplayName</key>
<string>Apple Intelligence Disable External Intelligence Logins</string>
<key>PayloadIdentifier</key>
<string>995CBF19-0AE8-4098-93A3-A87812366961</string>
<key>PayloadOrganization</key>
<string>Company Name</string>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>995CBF19-0AE8-4098-93A3-A87812366961</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>

Reading actual Wi-Fi hardware MAC address on macOS Sequoia

One of the privacy protections Apple introduced for macOS as part of macOS Sequoia is MAC address randomization. This address randomization is a privacy feature which Apple first introduced for mobile devices in iOS / iPadOS 14 and later, as well as watchOS 7 and later, which enables a unique randomly generated MAC address to be provided to Wi-Fi networks when an Apple device connects via Wi-Fi to the Wi-Fi network. The reason for doing this is to prevent the Apple device from being tracked as it connects to public Wi-Fi networks, as each Wi-Fi network will receive a new MAC address from the Apple device every time it connects to the Wi-Fi network in question.

For shops which want to disable the MAC address randomization for their own Wi-Fi networks, Apple has provided a DisableAssociationMACRandomization management setting which is available for use in iOS 14 and later, macOS 15 and later, and watchOS 7 and later. However, for shops which don’t want to disable this privacy protection but still want to be able to find out what the actual MAC address of the Wi-Fi network interface on Macs running macOS Sequoia and later, it’s possible to use the networksetup tool to do so. (Hat tip to everyone in the Mac Admins Slack who helped with figuring this out.) For more information, please see below the jump.

Assuming the Wi-Fi network interface on your Mac has been assigned the display name of Wi-Fi, you can get the actual MAC address using the following command:


/usr/sbin/networksetup -getmacaddress Wi-Fi

view raw

gistfile1.txt

hosted with ❤ by GitHub

 

You should see output similar to what’s shown below:


username@computername ~ % /usr/sbin/networksetup -getmacaddress Wi-Fi
Ethernet Address: 6c:ce:2e:d3:6b:bd (Hardware Port: Wi-Fi)
username@computername ~ %

view raw

gistfile1.txt

hosted with ❤ by GitHub

 

If you want only the MAC address returned, you can use the following command:


/usr/sbin/networksetup -getmacaddress $(/usr/sbin/networksetup -listallhardwareports | awk '/Hardware Port: Wi-Fi/{getline; print $2}') | awk '{print $3}'

view raw

gistfile1.txt

hosted with ❤ by GitHub

 

You should see output similar to what’s shown below:


username@computername ~ % /usr/sbin/networksetup -getmacaddress $(/usr/sbin/networksetup -listallhardwareports | awk '/Hardware Port: Wi-Fi/{getline; print $2}') | awk '{print $3}'
6c:ce:2e:d3:6b:bd
username@computername ~ %

view raw

gistfile1.txt

hosted with ❤ by GitHub

 

If you want only the MAC address returned with all the colons ( : ) removed, you can use the following command:


/usr/sbin/networksetup -getmacaddress $(/usr/sbin/networksetup -listallhardwareports | awk '/Hardware Port: Wi-Fi/{getline; print $2}') | awk '{print $3}' | tr -d ':'

view raw

gistfile1.txt

hosted with ❤ by GitHub

 

You should see output similar to what’s shown below:


username@computername ~ % /usr/sbin/networksetup -getmacaddress $(/usr/sbin/networksetup -listallhardwareports | awk '/Hardware Port: Wi-Fi/{getline; print $2}') | awk '{print $3}' | tr -d ':'
6cce2ed36bbd
username@computername ~ %

view raw

gistfile1.txt

hosted with ❤ by GitHub

Using Privileges post-actions

One of the options available in Privileges 2.x is the capability of running an action once admin rights have been granted or removed.

This action can be launching an app or running a script and is set by the Run after privilege change setting.

This action can be further customized by choosing to only run the action once admin rights have been granted. This can be set by the Run only if administrator privileges have been granted setting.

Something to be aware of is that when using an action is that the script or application in question will be run within the context of the logged-in user. This means it will have the same level of access rights that the logged-in user currently has (standard versus admin.) This may be important if running the script or launching the application includes functionality which works for a user with admin rights but not for a user with standard rights. An example of this is running the following command using the log command line tool:


log show –style syslog –predicate 'process BEGINSWITH "Privileges"' –last 1m

view raw

gistfile1.txt

hosted with ❤ by GitHub

If the logged-in user has admin rights, the log command shown above runs without issues and without requesting authentication.

If the logged-in user has standard rights, you get an error that the log command operation is not permitted.

Privileges 2.x includes management options for setting post-actions, so that their operation and configuration can be set using configuration profiles. For more details, please see below the jump.

The relevant preference domain and key values are listed below:

  • Preference domain: corp.sap.privileges
  • Key: PostChangeExecutablePath
  • Value: String containing the absolute filesystem path to an application or script
  • Preference domain: corp.sap.privileges
  • Key: PostChangeActionOnGrantOnly
  • Value: Boolean

Here’s how the settings would appear in the following example:

  1. The App Store app is launched as a post action.
  2. The action is run once admin rights have been granted or removed.


<?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"&gt;
<plist version="1.0">
<dict>
<key>PostChangeActionOnGrantOnly</key>
<false/>
<key>PostChangeExecutablePath</key>
<string>/System/Applications/App Store.app</string>
</dict>
</plist>

Here’s how the settings would appear in the following example:

  1. A script named privileges_teams_report.sh located in the /usr/local/bin directory is launched as a post action.
  2. The action is run only when admin rights have been granted.


<?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"&gt;
<plist version="1.0">
<dict>
<key>PostChangeActionOnGrantOnly</key>
<true/>
<key>PostChangeExecutablePath</key>
<string>/usr/local/bin/privileges_teams_report.sh</string>
</dict>
</plist>

One use case for a post action script would be sending a report to a Slack or Teams channel via webhook. While Privileges natively supports sending JSON output to a web hook, both Slack and Teams need to have the JSON being sent to it formatted in specific ways, or else the receiving end won’t be able to work with it. They’re also different formats, so sending to Slack using JSON formatted for Teams doesn’t work and vice-versa.

I’ve written a couple of example scripts which can be used with Privileges as a post action, which are designed to be run as follows:

  1. The reporting script is launched as a post action.
  2. The action is run only when admin rights have been granted.

Note: The reason why the reporting script should be run only when admin rights have been granted is that the log command line tool is used in the scripts. As discussed previously, these scripts will run in the context of the logged-in user and if the logged-in user has admin rights, the log command runs without issues and without requesting authentication. If the logged-in user has standard rights though, the log command will error.

privileges_slack_report.sh

When configured with a Slack webhook URL, the following script should send a report similar to the one below to the relevant Slack channel.


#!/bin/bash
# This script will send a report to a Slack channel with information about the user who has been granted admin rights.
# You'll need to set up a Slack webhook to receive the information being sent by the script.
# If you need help with configuring a Slack webhook, please see the links below:
#
# https://api.slack.com/incoming-webhooks
# https://get.slack.help/hc/en-us/articles/115005265063-Incoming-WebHooks-for-Slack
#
# Once a Slack webhook is available, the slack_webhook variable should look similar
# to this:
# slack_webhook="https://hooks.slack.com/services/XXXXXXXXX/YYYYYYYYY/ZZZZZZZZZZ&quot;
slack_webhook=""
# That should be it for the necessary configuration part.
# Nothing else should need to be edited below this line.
name=$(hostname)
logs=$(mktemp)
logged_in_user=$(id -un)
# Set script exit status
exit_error=0
# Function for sending multi-line output to a Slack webhook. Original script from here:
#
# http://blog.getpostman.com/2015/12/23/stream-any-log-file-to-slack-using-curl/
SendToSlack(){
cat "$1" | while read LINE; do
(echo "$LINE" | grep -e "$3") && curl -X POST –silent –data-urlencode "payload={\"text\": \"$(echo $LINE | sed "s/\"/'/g")\"}" "$2";
done
}
touch "$logs"
echo "Administrator rights have been granted to $logged_in_user using Privileges.app on $name:" >> "$logs"
/usr/bin/log show –style syslog –predicate 'process == "PrivilegesDaemon" && eventMessage BEGINSWITH "SAPCorp: U"' –last 1m | tail -1 >> "$logs"
SendToSlack ${logs} ${slack_webhook}
exit "$exit_error"

privileges_teams_report.sh

When configured with a Teams webhook URL, the following script should send a report similar to the one below to the relevant Teams channel.


#!/bin/bash
# This script will send a report to a Teams channel with information about the user who has been granted admin rights.
# You'll need to set up a Teams webhook to receive the information being sent by the script.
# If you need help with configuring a Teams webhook, please see the links below:
#
# https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook
#
#
# Once a Teams webhook is available, the teams_webhook variable should look similar
# to this:
# teams_webhook="https://companyname.webhook.office.com/webhookb2/7ce853bd-a9e1-462f-ae32-d3d35ed5295d@7c155bae-5207-4bb5-8b58-c43228bc1bb7/IncomingWebhook/8155d8581864479287b68b93f89556ae/651e63f8-2d96-42ab-bb51-65cb05fc62aa&quot;
teams_webhook=""
# That should be it for the necessary configuration part.
# Nothing else should need to be edited below this line.
name=$(hostname)
logs=$(mktemp)
logged_in_user=$(id -un)
# Set script exit status
exit_error=0
# Function for sending multi-line output to a Teams webhook. We add an extra Return to
# each line of the log file ($1) to prevent Teams from showing the log on a single line.
# The Teams Card format requires JSON to be sent to the Teams webhook ($2).
# You can add a title to the Card by specifying it as a third argument.
SendToTeams(){
LOG_TEXT=$( cat "$1" | sed "s/\"/'/g" | sed "s/$/\r\r/g" )
TEAMS_JSON='{
"type": "message",
"attachments": [{
"contentType": "application/vnd.microsoft.card.adaptive",
"contentUrl": null,
"content": {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json&quot;,
"type": "AdaptiveCard",
"version": "1.4",
"msteams": {"width": "Full"},
"body": [
{"type": "TextBlock", "text": "'$3'", "weight": "bolder", "size": "large", "wrap": true},
{"type": "TextBlock", "text": "'$LOG_TEXT'", "wrap": true},
]
}
}]}'
/usr/bin/curl -H "Content-Type: application/json" -d "${TEAMS_JSON}" "$2"
}
touch "$logs"
echo "Administrator rights have been granted to $logged_in_user using Privileges.app on $name:" >> "$logs"
/usr/bin/log show –style syslog –predicate 'process == "PrivilegesDaemon" && eventMessage BEGINSWITH "SAPCorp: U"' –last 1m | tail -1 >> "$logs"
SendToTeams ${logs} ${teams_webhook} "Privileges.app Report"
exit "$exit_error"

Example configuration profiles containing the PostChangeExecutablePath and PostChangeActionOnGrantOnly settings are available below:

ConfigurePrivilegesPostActionAppStore.mobileconfig

Settings:

  1. The App Store app is launched as a post action.
  2. The action is run once admin rights have been granted or removed.


<?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"&gt;
<plist version="1">
<dict>
<key>PayloadUUID</key>
<string>2857BC2F-FC70-4F9E-B5AE-F1DC56ECC79B</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadOrganization</key>
<string>Company Name</string>
<key>PayloadIdentifier</key>
<string>2857BC2F-FC70-4F9E-B5AE-F1DC56ECC79B</string>
<key>PayloadDisplayName</key>
<string>Configure Privileges Post Action App Store</string>
<key>PayloadDescription</key>
<string>Configure Privileges to launch the Mac App Store as a post action.</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadEnabled</key>
<true/>
<key>PayloadRemovalDisallowed</key>
<true/>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadDisplayName</key>
<string>Custom Settings</string>
<key>PayloadIdentifier</key>
<string>C667AE87-34F2-4CC4-B87E-70242EFD6E50</string>
<key>PayloadOrganization</key>
<string>Company Name</string>
<key>PayloadType</key>
<string>com.apple.ManagedClient.preferences</string>
<key>PayloadUUID</key>
<string>C667AE87-34F2-4CC4-B87E-70242EFD6E50</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadContent</key>
<dict>
<key>corp.sap.privileges</key>
<dict>
<key>Forced</key>
<array>
<dict>
<key>mcx_preference_settings</key>
<dict>
<key>PostChangeActionOnGrantOnly</key>
<false/>
<key>PostChangeExecutablePath</key>
<string>/System/Applications/App Store.app</string>
</dict>
</dict>
</array>
</dict>
</dict>
</dict>
</array>
</dict>
</plist>

ConfigurePrivilegesPostActionReportScript.mobileconfig

  1. A script named reporting_script.sh located in the /path/to directory is launched as a post action.
  2. The action is run only when admin rights have been granted.


<?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"&gt;
<plist version="1">
<dict>
<key>PayloadUUID</key>
<string>3DD0FB5F-F238-455D-AF43-843BB4111126</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadOrganization</key>
<string>Company Name</string>
<key>PayloadIdentifier</key>
<string>3DD0FB5F-F238-455D-AF43-843BB4111126</string>
<key>PayloadDisplayName</key>
<string>Configure Privileges Post Action Report Script</string>
<key>PayloadDescription</key>
<string>Configure Privileges to launch a reporting script as a post action only when admin rights are granted.</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadEnabled</key>
<true/>
<key>PayloadRemovalDisallowed</key>
<true/>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadDisplayName</key>
<string>Custom Settings</string>
<key>PayloadIdentifier</key>
<string>B4D8564A-217C-42E3-AD69-23967AF22B84</string>
<key>PayloadOrganization</key>
<string>Company Name</string>
<key>PayloadType</key>
<string>com.apple.ManagedClient.preferences</string>
<key>PayloadUUID</key>
<string>B4D8564A-217C-42E3-AD69-23967AF22B84</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadContent</key>
<dict>
<key>corp.sap.privileges</key>
<dict>
<key>Forced</key>
<array>
<dict>
<key>mcx_preference_settings</key>
<dict>
<key>PostChangeActionOnGrantOnly</key>
<true/>
<key>PostChangeExecutablePath</key>
<string>/path/to/reporting_script.sh</string>
</dict>
</dict>
</array>
</dict>
</dict>
</dict>
</array>
</dict>
</plist>

Managing time limited admin rights with Privileges 2.x

One of the new features in Privileges 2.x is the unified ability to set and manage how long you want to grant admin rights, where running Privileges grants admin rights for a defined amount of time and then those admin rights are taken away. This ability had also existed in Privileges 1.x but it was exclusively tied to Privileges 1.x’s Toggle Privileges function, where in Privileges 2.x it is available no matter how Privileges is being run.

By default, Privileges 2.x will grant administrator rights for 20 minutes if not configured otherwise.

 

But what if you want to configure it otherwise? There are management options available for this. For more details, please see below the jump.

 

 

The relevant preference domain and key values are listed below:

  • Preference domain: corp.sap.privileges
  • Key: ExpirationInterval
  • Value: Positive Integer

 


<?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"&gt;
<plist version="1.0">
<dict>
<key>ExpirationInterval</key>
<integer>15</integer>
</dict>
</plist>

 

 

  • Preference domain: corp.sap.privileges
  • Key: ExpirationIntervalMax
  • Value: Positive Integer

 

 


<?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"&gt;
<plist version="1.0">
<dict>
<key>ExpirationIntervalMax</key>
<integer>20</integer>
</dict>
</plist>

 

Note: In both cases, the positive integer values are defining time in minutes.

 

 

ExpirationInterval:

The ExpirationInterval key defines a set time in minutes after which administrator rights expire and the logged-in user reverts to using standard user rights. For example, setting ExpirationInterval to a value of 15 would set Privileges to allow admin rights for fifteen minutes. Once the fifteen minutes are up, the logged-in user reverts to using standard user rights.

In this example, the Administrator privileges expire setting in the Privileges settings would be set to the defined value and grayed out.

Note: Setting a value of 0 disables the timeout and allows the user to request administrator privileges which do not expire.

 

 

ExpirationIntervalMax:

The ExpirationIntervalMax key defines a set time in minutes after which administrator rights expire and the logged-in user reverts to using standard user rights. In general, this works like the ExpirationInterval key but it allows the logged-in user to choose a timeout value which is different as long it does not exceed the defined value.

For example, setting ExpirationIntervalMax to a value of 20 would set Privileges to allow admin rights for twenty minutes. However, the logged-in user can go into the Privileges settings and set a different time interval for the Administrator privileges expire setting as long as that time interval does not exceed the defined value of twenty minutes.

Note: In the event that both the ExpirationInterval and ExpirationIntervalMax settings are set, as of Privileges 2.0 the ExpirationInterval behavior will be applied. The Administrator privileges expire setting in the Privileges settings would be set to the defined value for ExpirationInterval and be grayed out.

 

 

The ExpirationInterval and ExpirationIntervalMax settings can be managed by configuration profiles. Please see below for example profiles.

 

ExpirationInterval:

 


<?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"&gt;
<plist version="1">
<dict>
<key>PayloadUUID</key>
<string>B3D51AB8-3307-4CBA-B5B7-0CB590D62797</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadOrganization</key>
<string>Company Name</string>
<key>PayloadIdentifier</key>
<string>B3D51AB8-3307-4CBA-B5B7-0CB590D62797</string>
<key>PayloadDisplayName</key>
<string>Configure Privileges Admin Rights Removal Time</string>
<key>PayloadDescription</key>
<string>Configure Privileges to remove admin rights after the defined time.</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadEnabled</key>
<true/>
<key>PayloadRemovalDisallowed</key>
<true/>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadDisplayName</key>
<string>Custom Settings</string>
<key>PayloadIdentifier</key>
<string>FF229F24-BF06-4CA6-AD95-500A649893FE</string>
<key>PayloadOrganization</key>
<string>Company Name</string>
<key>PayloadType</key>
<string>com.apple.ManagedClient.preferences</string>
<key>PayloadUUID</key>
<string>FF229F24-BF06-4CA6-AD95-500A649893FE</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadContent</key>
<dict>
<key>corp.sap.privileges</key>
<dict>
<key>Forced</key>
<array>
<dict>
<key>mcx_preference_settings</key>
<dict>
<key>ExpirationInterval</key>
<integer>15</integer>
</dict>
</dict>
</array>
</dict>
</dict>
</dict>
</array>
</dict>
</plist>

 

ExpirationIntervalMax:

 


<?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"&gt;
<plist version="1">
<dict>
<key>PayloadUUID</key>
<string>71512933-DEB5-4628-BFD9-2BFFF90674E9</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadOrganization</key>
<string>Company Name</string>
<key>PayloadIdentifier</key>
<string>71512933-DEB5-4628-BFD9-2BFFF90674E9</string>
<key>PayloadDisplayName</key>
<string>Configure User Adjustable Privileges Admin Rights Removal Time</string>
<key>PayloadDescription</key>
<string>Configure Privileges to remove admin rights after the defined time with an option for the user to choose a shorter time.</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadEnabled</key>
<true/>
<key>PayloadRemovalDisallowed</key>
<true/>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadDisplayName</key>
<string>Custom Settings</string>
<key>PayloadIdentifier</key>
<string>AD2F05BF-B01C-430F-A395-BEE34A6689C2</string>
<key>PayloadOrganization</key>
<string>Company Name</string>
<key>PayloadType</key>
<string>com.apple.ManagedClient.preferences</string>
<key>PayloadUUID</key>
<string>AD2F05BF-B01C-430F-A395-BEE34A6689C2</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadContent</key>
<dict>
<key>corp.sap.privileges</key>
<dict>
<key>Forced</key>
<array>
<dict>
<key>mcx_preference_settings</key>
<dict>
<key>ExpirationIntervalMax</key>
<integer>20</integer>
</dict>
</dict>
</array>
</dict>
</dict>
</dict>
</array>
</dict>
</plist>

Privileges 2.0 available with new features

SAP has released a new major version of their open source Privileges app. This tool provides macOS standard user accounts with a way to request administrator rights. Privileges 2.x includes a number of new features not available in Privileges 1.x and in particular fulfills two particular long-standing requests from its user community:

  1. It provides a unified mechanism for time-limited admin rights.
  2. SAP now provides a signed and notarized installer package for deployment.

For more details, please see below the jump.

Time-limited admin

Privileges 1.x featured a mechanism for setting a time limit, but it was tied specifically to using the Toggle Privileges function. This was discussed in the previous version of the Privileges FAQ:

By default, is there a time limit on the admin rights granted by Privileges?

No. Admin rights are granted until some process (like running Privileges again) takes them away.

Can I set Privileges to give me administrator rights for a defined amount of time?

Yes. You can use the Toggle Privileges option on the dock icon to get admin rights for a set amount of time (the default amount is 20 minutes.)

Screen Shot 2022-07-22 at 10.05.50 AM.

With Privileges 2.x, time-limited admin is no longer tied exclusively to the Toggle Privileges function. For those who want to set a time limit for granting admin rights, you can now set this and Privileges 2.x will remove admin rights after the set time regardless of if you used the Privileges application, the dock tile or the command line tool to request admin rights.

By default, Privileges 2.x will grant administrator privileges for 20 minutes if not configured otherwise.

This is discussed in the updated Privileges FAQ:

By default, is there a time limit on the admin rights granted by Privileges?

Yes. By default, administrator privileges are granted for 20 minutes (if not configured otherwise). However, if necessary, you can configure Privileges not to remove administrator privileges by setting the expiration interval to “Never” in the app’s settings.

Installer package deployment

Privileges 1.x had an odd issue, where some folks who tried packaging it into an installer package consistently ran into problems. This was partially addressed by using AutoPkg to build the installer package, as AutoPkg-driven workflows consistently produced working installers. SAP has addressed this issue by providing a signed and notarized installer package for Privileges 2.x, which solves the problem by making it unnecessary for Mac admins to create their own installer packages for deployment.

For those using Privileges in your own shops, I recommend taking a look at Privileges 2.x as it includes more features and fixes in addition to what I’ve discussed above. It is available via the following link:

https://github.com/SAP/macOS-enterprise-privileges

For those who want to manage Privileges, please see here for the Managing Privileges documentation:

https://github.com/SAP/macOS-enterprise-privileges/wiki/Managing-Privileges

Accessing the recovery key password reset option at the login window on macOS Sequoia

If the following situation occurs:

  1. You forgot the password to the local account you use to log into your Mac.
  2. You have FileVault enabled.
  3. You have the FileVault recovery key available.

You can use the FileVault recovery key to authenticate changing your local account to use a new password. Apple has documentation on how to do this available here:

https://support.apple.com/102633 (please see the Reset it using your recovery key instructions)

However, it looks like Apple made a change at the login window for macOS Sequoia. Apple’s instructions reference clicking on a ( ? ) symbol, which doesn’t appear in my testing on Apple Silicon Macs. Without that, how do you access the recovery key entry blank to enter the recovery key?

In the absence of the ( ? ) symbol appearing at the login window, you should be able to use the following keyboard shortcut to get the recovery key entry blank:

Shift+Option+Return

Apple keyboard keys highlighted.

Clicking that combination of keyboard keys on an Apple Silicon Mac should cause the recovery key entry blank to appear at the login screen. 

Note: I was not able to verify that this also works on Intel Macs, so please let me know in the comments if Intel Macs have different behavior.

Here’s how the login window should appear when you enter the keyboard shortcut in this scenario:

Clearing failed MDM commands from members of Jamf Pro smart or static groups

A while back, I had posted about a solution for clearing failed MDM commands on a per-computer basis. I recently learned it’s also possible to clear them by using an API command which clears failed MDM commands from all members of a specified Jamf Pro smart or static group. This approach works for both computer groups and mobile device groups. For example, if you wanted to clear all failed MDM commands for members of a mobile device group, you could use a command like the one shown below:

view raw

gistfile1.txt

hosted with ❤ by GitHub

If you wanted to clear all failed MDM commands for members of a computer group, you could use a command like the one shown below:


/usr/bin/curl -sf –header "Authorization: Bearer bearer_token_goes_here" "https://jamf.pro.server.goes.here/JSSResource/commandflush/computergroups/id/group_Jamf_Pro_ID_number_goes_here/status/Failed&quot; -X DELETE

view raw

gistfile1.txt

hosted with ❤ by GitHub

In both cases, the following API permission would be required:

Flush MDM Commands

If using a user account to authenticate to the API, this permission would be set in Jamf Pro Server Actions:

If using an API client to authenticate to the API, this permission would be set in an API role:

For folks who want to use this method to clear failed API commands, I’ve written a couple of scripts to assist with this. For more details, please see below the jump.

I’ve posted both scripts to the following location:

https://github.com/rtrouton/rtrouton_scripts/tree/main/rtrouton_scripts/Casper_Scripts/clear_failed_Jamf_Pro_mdm_commands_from_groups

  • clear_failed_Jamf_Pro_mdm_commands_from_computer_group.sh – clears failed MDM commands from Jamf Pro smart or static computer groups
  • clear_failed_Jamf_Pro_mdm_commands_from_mobile_device_group.sh – clears failed MDM commands from Jamf Pro smart or static mobile device groups

Both scripts are designed to use API client authentication, with the following permissions assigned:

clear_failed_Jamf_Pro_mdm_commands_from_computer_group.sh

  • Flush MDM Commands
  • Read Smart Computer Groups
  • Read Static Computer Groups

clear_failed_Jamf_Pro_mdm_commands_from_mobile_device_group.sh

  • Flush MDM Commands
  • Read Smart Mobile Device Groups
  • Read Static Mobile Device Groups

Both scripts are designed to use the Jamf Pro ID number for a specified Jamf Pro smart or static group to do the following:

  1. Verify that the provided Jamf Pro ID is a positive number, as Jamf Pro IDs should only be only numbers which are not negative.
  2. If the provided Jamf Pro ID is a positive number, look up the display name of the specified Jamf Pro smart or static group via the Jamf Pro Classic API using the Jamf Pro ID number.
  3. If the lookup succeeds, send a command to clear all failed MDM commands associated with the members of the specified group.
  4. If the MDM command clearing succeeds, display a message that all failed MDM commands associated with the members of the specified group have been cleared.

The scripts will produce errors in the following cases:

  1. The provided Jamf Pro ID is not a positive number.
  2. The lookup of the display name of the specified Jamf Pro smart or static group fails.
  3. The MDM command clearing fails.

Successful output should look like this for the following scripts:

clear_failed_Jamf_Pro_mdm_commands_from_computer_group.sh


username@computername ~ % /path/to/clear_failed_Jamf_Pro_mdm_commands_from_computer_group.sh
Please enter your Jamf Pro server URL : https://jamf.pro.server.goes.here
Please enter your Jamf Pro API client ID : 7eda98e3-12ea-469c-8c45-4e070b5003cb
Please enter the API client secret for the 7eda98e3-12ea-469c-8c45-4e070b5003cb API ID client:
The smart or static computer group you want to clear failed MDM commands from has not been specified.
Please enter the Jamf Pro ID of the smart or static computer group : 1
Clearing failed MDM commmands from members of the following group: All Managed Clients
<?xml version="1.0" encoding="UTF-8"?><commandflush><status>+failed</status><computer_groups>[1]</computer_groups></commandflush>
Failed MDM commands successfully cleared from members of the following group: All Managed Clients
username@computername ~ %

clear_failed_Jamf_Pro_mdm_commands_from_mobile_device_group.sh


username@computername ~ % /path/to/clear_failed_Jamf_Pro_mdm_commands_from_mobile_device_group.sh
Please enter your Jamf Pro server URL : https://jamf.pro.server.goes.here
Please enter your Jamf Pro API client ID : 7eda98e3-12ea-469c-8c45-4e070b5003cb
Please enter the API client secret for the 7eda98e3-12ea-469c-8c45-4e070b5003cb API ID client:
The smart or static mobile device group you want to clear failed MDM commands from has not been specified.
Please enter the Jamf Pro ID of the smart or static mobile device group : 1
Clearing failed MDM commmands from members of the following group: All Managed Apple TVs
<?xml version="1.0" encoding="UTF-8"?><commandflush><status>+failed</status><mobile_device_groups>[1]</mobile_device_groups></commandflush>
Failed MDM commands successfully cleared from members of the following group: All Managed Apple TVs
username@computername ~ %