Session videos and slides available from MacSysAdmin 2024

The documentation from MacSysAdmin 2024 is available, with the session slides and videos being accessible from the link below:

http://documentation.macsysadmin.se

The video of my session is available for download from here:

macOS Application Packaging 101https://docs.macsysadmin.se/2024/video/Day1Session6.mp4

I also like to thank Patrik Jerneheim again for inviting me to speak at this year’s MacSysAdmin.

Slides from the “macOS application packaging 101” session at MacSysAdmin 2024

For those who wanted a copy of my installer packaging talk at the MacSysAdmin 2024 conference, here are links to the slides in PDF and Keynote format.

Moving on to a new place

At the end of July, I made the following announcement via Twitter:

https://x.com/rtrouton/status/1815414758184558984

Screenshot 2024-09-27 at 2.28.33 PM

Time has marched on and today is my last day at SAP. I wanted to take the opportunity to express my gratitude to my team. I am proud of what we were able to do together and wish each one of you every success for the future.

One of the things that I valued greatly while I was at SAP was that I was encouraged to write (both for this blog and my first book), speak at conferences and be involved with the Mac admin community. Thank you to everyone at SAP who provided this encouragement and made sure I always had what I needed.

I look forward to continuing to speak, write and be engaged with community work as part of my move to Jamf, where I’m joining a great team doing amazing things for Jamf’s customers. May the next seven years be as good (or better!) as the past seven.

Updating asset tag information in Jamf Pro using the Jamf Pro Classic API

A while back, I needed to update Macs with asset tag numbers with information from a .csv file, where the .csv file contained the following information:

  • The Mac’s hardware serial number
  • Asset tag number

To handle this task, I wrote a script which uses the Jamf Pro Classic API to look up the relevant computer inventory record in Jamf Pro using the Mac’s serial number and then update the computer inventory record with the matching asset tag number. For more details, please see below the jump.

This script reads a .csv file formatted as follows:

Serial number, Asset number as the first line

Subsequent lines:

  • Column 1: A Mac’s hardware serial number
  • Column 2: An inventory asset code

Example file:



Serial number Asset number
W8810X481AX 1652
W89020U8289 1978
CK1243R4DB6 2011

 

This script is designed to run as shown below:

/path/to/Jamf_Pro_Inventory_Asset_Tag_Update.sh filename_goes_here.csv

Once executed, the script will then do the following:

  1. Skip the first line of the .csv file (this is the Serial number, Asset number line.)
  2. Read each subsequent line of the .csv one at a time and assign the values of column 1 and column 2 to separate variables.
  3. Use the variables in an API PUT call to identify a Jamf Pro computer inventory record using the Mac hardware serial number listed in the .csv file and populate the asset tag information using the inventory asset code listed in the .csv file.

Successful update of the asset tag information should produce output similar to that shown below:


username@computername ~ % /path/to/Jamf_Pro_Inventory_Asset_Tag_Update.sh /path/to/asset_tag_information.csv
Please enter your Jamf Pro server URL : https://jamf.pro.server.goes.here
Please enter your Jamf Pro user account : username_goes_here
Please enter the password for the username_goes_here account:
Successfully updated computer record with serial number W8810X481AX with asset number 1652
Successfully updated computer record with serial number W89020U8289 with asset number 1978
Successfully updated computer record with serial number CK1243R4DB6 with asset number 2011
username@computername ~ %

view raw

gistfile1.txt

hosted with ❤ by GitHub

 

If setting up a specific user account with limited rights, here are the required API privileges for the account on the Jamf Pro server:

Jamf Pro Server Objects:

Computers: Read, Update

 

The script is available below, and from GitHub at the following location:

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

 


#!/bin/bash
# Updates asset tag information in Jamf Pro.
# This script reads a .csv file formatted as follows:
#
# "Serial number, Asset number" as the first line
#
# Subsequent lines:
# Column 1: A Mac's serial number
# Column 2: An inventory asset code
#
# Example:
#
# Serial number, Asset number
# W8810X481AX,1652
# W89020U8289,1978
# CK1243R4DB6,2011
#
# This script is designed to run as shown below:
#
# /path/to/Jamf_Pro_Inventory_Asset_Tag_Update.sh filename_goes_here.csv
#
# Once executed, the script will then do the following:
#
# Skip the first line of the .csv file (this is the "Serial number, Asset number" line.)
# Read each subsequent line of the .csv one at a time and assign the values of column 1
# and column 2 to separate variables.
#
# Use the variables in an API PUT call to identify a Jamf Pro
# computer inventory record using the serial number listed in
# the .csv file and populate the asset tag information using
# the inventory asset code listed in the .csv file.
#
# Display HTTP return code and API output
#
# Successful asset update should produce output similar to that shown below:
#
# Successfully updated computer record with serial number W8810X481AX with asset number 1652
# Successfully updated computer record with serial number W89020U8289 with asset number 1978
# Successfully updated computer record with serial number CK1243R4DB6 with asset number 2011
#
# If setting up a specific user account with limited rights, here are the required API privileges
# for the account on the Jamf Pro server:
#
# Jamf Pro Server Objects:
#
# Computers: Read, Update
# If you're on Jamf Pro 10.34.2 or earlier, which doesn't support using Bearer Tokens
# for Classic API authentication, set the NoBearerToken variable to the following value
# as shown below:
#
# yes
#
# NoBearerToken="yes"
#
# If you're on Jamf Pro 10.35.0 or later, which does support using Bearer Tokens
# for Classic API authentication, set the NoBearerToken variable to the following value
# as shown below:
#
# NoBearerToken=""
NoBearerToken=""
GetJamfProAPIToken() {
# This function uses Basic Authentication to get a new bearer token for API authentication.
# Use user account's username and password credentials with Basic Authorization to request a bearer token.
if [[ $(/usr/bin/sw_vers -productVersion | awk -F . '{print $1}') -lt 12 ]]; then
api_token=$(/usr/bin/curl -X POST –silent -u "${jamfpro_user}:${jamfpro_password}" "${jamfpro_url}/api/v1/auth/token" | python -c 'import sys, json; print json.load(sys.stdin)["token"]')
else
api_token=$(/usr/bin/curl -X POST –silent -u "${jamfpro_user}:${jamfpro_password}" "${jamfpro_url}/api/v1/auth/token" | plutil -extract token raw -)
fi
}
APITokenValidCheck() {
# Verify that API authentication is using a valid token by running an API command
# which displays the authorization details associated with the current API user.
# The API call will only return the HTTP status code.
api_authentication_check=$(/usr/bin/curl –write-out %{http_code} –silent –output /dev/null "${jamfpro_url}/api/v1/auth" –request GET –header "Authorization: Bearer ${api_token}")
}
CheckAndRenewAPIToken() {
# Verify that API authentication is using a valid token by running an API command
# which displays the authorization details associated with the current API user.
# The API call will only return the HTTP status code.
APITokenValidCheck
# If the api_authentication_check has a value of 200, that means that the current
# bearer token is valid and can be used to authenticate an API call.
if [[ ${api_authentication_check} == 200 ]]; then
# If the current bearer token is valid, it is used to connect to the keep-alive endpoint. This will
# trigger the issuing of a new bearer token and the invalidation of the previous one.
if [[ $(/usr/bin/sw_vers -productVersion | awk -F . '{print $1}') -lt 12 ]]; then
api_token=$(/usr/bin/curl "${jamfpro_url}/api/v1/auth/keep-alive" –silent –request POST –header "Authorization: Bearer ${api_token}" | python -c 'import sys, json; print json.load(sys.stdin)["token"]')
else
api_token=$(/usr/bin/curl "${jamfpro_url}/api/v1/auth/keep-alive" –silent –request POST –header "Authorization: Bearer ${api_token}" | plutil -extract token raw -)
fi
else
# If the current bearer token is not valid, this will trigger the issuing of a new bearer token
# using Basic Authentication.
GetJamfProAPIToken
fi
}
InvalidateToken() {
# Verify that API authentication is using a valid token by running an API command
# which displays the authorization details associated with the current API user.
# The API call will only return the HTTP status code.
APITokenValidCheck
# If the api_authentication_check has a value of 200, that means that the current
# bearer token is valid and can be used to authenticate an API call.
if [[ ${api_authentication_check} == 200 ]]; then
# If the current bearer token is valid, an API call is sent to invalidate the token.
authToken=$(/usr/bin/curl "${jamfpro_url}/api/v1/auth/invalidate-token" –silent –header "Authorization: Bearer ${api_token}" -X POST)
# Explicitly set value for the api_token variable to null.
api_token=""
fi
}
# If you choose to hardcode API information into the script, set one or more of the following values:
#
# The username for an account on the Jamf Pro server with sufficient API privileges
# The password for the account
# The Jamf Pro URL
# Set the Jamf Pro URL here if you want it hardcoded.
jamfpro_url=""
# Set the username here if you want it hardcoded.
jamfpro_user=""
# Set the password here if you want it hardcoded.
jamfpro_password=""
# If you do not want to hardcode API information into the script, you can also store
# these values in a ~/Library/Preferences/com.github.jamfpro-info.plist file.
#
# To create the file and set the values, run the following commands and substitute
# your own values where appropriate:
#
# To store the Jamf Pro URL in the plist file:
# defaults write com.github.jamfpro-info jamfpro_url https://jamf.pro.server.goes.here:port_number_goes_here
#
# To store the account username in the plist file:
# defaults write com.github.jamfpro-info jamfpro_user account_username_goes_here
#
# To store the account password in the plist file:
# defaults write com.github.jamfpro-info jamfpro_password account_password_goes_here
#
# If the com.github.jamfpro-info.plist file is available, the script will read in the
# relevant information from the plist file.
jamfpro_plist="$HOME/Library/Preferences/com.github.jamfpro-info.plist"
filename="$1"
exitCode=0
if [[ -r "$jamfpro_plist" ]]; then
if [[ -z "$jamfpro_url" ]]; then
jamfpro_url=$(defaults read "${jamfpro_plist%.*}" jamfpro_url)
fi
if [[ -z "$jamfpro_user" ]]; then
jamfpro_user=$(defaults read "${jamfpro_plist%.*}" jamfpro_user)
fi
if [[ -z "$jamfpro_password" ]]; then
jamfpro_password=$(defaults read "${jamfpro_plist%.*}" jamfpro_password)
fi
fi
# If the Jamf Pro URL, the account username or the account password aren't available
# otherwise, you will be prompted to enter the requested URL or account credentials.
if [[ -z "$jamfpro_url" ]]; then
read -p "Please enter your Jamf Pro server URL : " jamfpro_url
fi
if [[ -z "$jamfpro_user" ]]; then
read -p "Please enter your Jamf Pro user account : " jamfpro_user
fi
if [[ -z "$jamfpro_password" ]]; then
read -p "Please enter the password for the $jamfpro_user account: " -s jamfpro_password
fi
echo ""
# Remove the trailing slash from the Jamf Pro URL if needed.
jamfpro_url=${jamfpro_url%%/}
# If configured to get one, get a Jamf Pro API Bearer Token
if [[ -z "$NoBearerToken" ]]; then
GetJamfProAPIToken
fi
if [[ -n $filename && -r $filename ]]; then
while IFS=, read serial_number asset_number || [ -n "$serial_number" ]; do
if [[ -z "$NoBearerToken" ]]; then
CheckAndRenewAPIToken
apiResponse=$(/usr/bin/curl -s –header "Authorization: Bearer ${api_token}" "${jamfpro_url}/JSSResource/computers/serialnumber/${serial_number}" -w "<http_status>%{http_code}</http_status>" -H "Content-Type: application/xml" -X PUT -d "<computer><general><asset_tag>${asset_number}</asset_tag></general></computer>")
else
apiResponse=$(/usr/bin/curl -su "${jamfpro_user}:${jamfpro_password}" "${jamfpro_url}/JSSResource/computers/serialnumber/${serial_number}" -w "<http_status>%{http_code}</http_status>" -H "Content-Type: application/xml" -X PUT -d "<computer><general><asset_tag>${asset_number}</asset_tag></general></computer>")
fi
responseCode=$(echo "$apiResponse" | /usr/bin/sed -n 's/.*<http_status>\([^<]*\).*/\1/p')
if [[ "$responseCode" != "201" ]]; then
echo "Update of computer record with serial number $serial_number failed with http error $responseCode"
else
echo "Successfully updated computer record with serial number $serial_number with asset number $asset_number"
fi
done < <(tail -n +2 "$filename")
else
echo "Input file does not exist or is not readable"
exitCode=1
fi
exit "$exitCode"

Managing Gatekeeper with configuration profiles on macOS Sequoia

Now that the spctl tool can no longer separately manage Gatekeeper, management profiles are the best way to manage Gatekeeper on macOS Sequoia. For more details, please see below the jump.

On macOS Sequoia, the following management options are available (all use boolean values of true and false):

AllowIdentifiedDevelopers

  • If set to true, this setting enables Gatekeeper’s Mac App Store and identified developers option.
  • If set to false, this setting enables Gatekeeper’s Mac App Store option, which restricts Gatekeeper to only accepting apps from Apple’s Mac App Store.

 

EnableAssessment

  • If set to true, this setting enables Gatekeeper to assess applications. This works in combination with the AllowIdentifiedDevelopers setting described above.
  • If set to false, this setting sets Gatekeeper to accept all apps (effectively disabling Gatekeeper.)

Please see below for example profiles:

 

Disable Gatekeeper:


<?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>EnableAssessment</key>
<false/>
<key>PayloadDisplayName</key>
<string>System Policy Control #1</string>
<key>PayloadIdentifier</key>
<string>com.apple.systempolicy.control.A64CB883-59A7-4603-9B45-F4863ADE8E18</string>
<key>PayloadType</key>
<string>com.apple.systempolicy.control</string>
<key>PayloadUUID</key>
<string>A64CB883-59A7-4603-9B45-F4863ADE8E18</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</array>
<key>PayloadDisplayName</key>
<string>Disable Gatekeeper</string>
<key>PayloadIdentifier</key>
<string>com.company.21516E89-BABE-4834-A3F4-CCA83B144124</string>
<key>PayloadOrganization</key>
<string>Company Name</string>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>21516E89-BABE-4834-A3F4-CCA83B144124</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>

 

 

Enable Gatekeeper and allow Identified Developers:


<?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>AllowIdentifiedDevelopers</key>
<true/>
<key>EnableAssessment</key>
<true/>
<key>PayloadDisplayName</key>
<string>System Policy Control #1</string>
<key>PayloadIdentifier</key>
<string>com.apple.systempolicy.control.A64CB883-59A7-4603-9B45-F4863ADE8E18</string>
<key>PayloadType</key>
<string>com.apple.systempolicy.control</string>
<key>PayloadUUID</key>
<string>A64CB883-59A7-4603-9B45-F4863ADE8E18</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</array>
<key>PayloadDisplayName</key>
<string>Enable Gatekeeper and allow Identified Developers</string>
<key>PayloadIdentifier</key>
<string>com.company.26D1851E-1929-43BA-980D-07678292B533</string>
<key>PayloadOrganization</key>
<string>Company Name</string>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>26D1851E-1929-43BA-980D-07678292B533</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>

 

 

Enable Gatekeeper and not allow Identified Developers:


<?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>AllowIdentifiedDevelopers</key>
<false/>
<key>EnableAssessment</key>
<true/>
<key>PayloadDisplayName</key>
<string>System Policy Control #1</string>
<key>PayloadIdentifier</key>
<string>com.apple.systempolicy.control.A64CB883-59A7-4603-9B45-F4863ADE8E18</string>
<key>PayloadType</key>
<string>com.apple.systempolicy.control</string>
<key>PayloadUUID</key>
<string>A64CB883-59A7-4603-9B45-F4863ADE8E18</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</array>
<key>PayloadDisplayName</key>
<string>Enable Gatekeeper and not allow Identified Developers</string>
<key>PayloadIdentifier</key>
<string>com.company.ECDF666F-943A-4530-8725-4D68624E687A</string>
<key>PayloadOrganization</key>
<string>Company Name</string>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>ECDF666F-943A-4530-8725-4D68624E687A</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>

 

 

Both of these management options (AllowIdentifiedDevelopers and EnableAssessment) have been around for a while, but there is a new management option for macOS Sequoia. On Sequoia, Gatekeeper can now prompt the user to upload blocked malware to Apple for Apple to analyze. As part of this, Apple added the following management option:

 

EnableXProtectMalwareUpload

  • If set to true, this setting enables Gatekeeper to prompt the user to upload blocked malware to Apple.
  • If set to false, this setting prevents Gatekeeper from prompting the user to upload blocked malware to Apple.

Please see below for example profiles:

 

Allow XProtect Malware Upload:


<?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>EnableXProtectMalwareUpload</key>
<true/>
<key>PayloadDisplayName</key>
<string>System Policy Control #1</string>
<key>PayloadIdentifier</key>
<string>com.apple.systempolicy.control.7C454761-633D-43A1-8007-D8F4CCE2EAD5</string>
<key>PayloadType</key>
<string>com.apple.systempolicy.control</string>
<key>PayloadUUID</key>
<string>7C454761-633D-43A1-8007-D8F4CCE2EAD5</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</array>
<key>PayloadDisplayName</key>
<string>Allow XProtect Malware Upload</string>
<key>PayloadIdentifier</key>
<string>com.company.51C1F0F8-1B72-4445-BEFF-8901AADE2CFC</string>
<key>PayloadOrganization</key>
<string>Company Name</string>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>51C1F0F8-1B72-4445-BEFF-8901AADE2CFC</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>

 

 

Block XProtect Malware Upload:


<?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>EnableXProtectMalwareUpload</key>
<false/>
<key>PayloadDisplayName</key>
<string>System Policy Control #1</string>
<key>PayloadIdentifier</key>
<string>com.apple.systempolicy.control.4CB2A13D-B8D2-4EA9-986E-B29A757805DA</string>
<key>PayloadType</key>
<string>com.apple.systempolicy.control</string>
<key>PayloadUUID</key>
<string>4CB2A13D-B8D2-4EA9-986E-B29A757805DA</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</array>
<key>PayloadDisplayName</key>
<string>Block XProtect Malware Upload</string>
<key>PayloadIdentifier</key>
<string>com.company.C184E62D-C41F-41CF-BA76-AE24EDC9C0D6</string>
<key>PayloadOrganization</key>
<string>Company Name</string>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>C184E62D-C41F-41CF-BA76-AE24EDC9C0D6</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>

spctl command line tool no longer able to manage Gatekeeper on macOS Sequoia

On macOS Sonoma and earlier versions of macOS, the spctl command line tool could be used to turn Gatekeeper on and off. For example, on macOS Sonoma the following command can be run with root privileges to disable Gatekeeper:


/usr/sbin/spctl –global-disable

view raw

gistfile1.txt

hosted with ❤ by GitHub

 

After running this command, in System Settings: Privacy and Security, you should see Gatekeeper displaying that it is set to Anywhere:

 

Also on macOS Sonoma, the following command can be run with root privileges to enable Gatekeeper:


/usr/sbin/spctl –global-enable

view raw

gistfile1.txt

hosted with ❤ by GitHub

 

After running this command, in System Settings: Privacy and Security, you should see Gatekeeper displaying that it is set to App Store and identified developers:

 

On macOS Sequoia, this Gatekeeper management approach no longer works. For more details, please see below the jump.

 

 

On macOS Sequoia, running the command to disable Gatekeeper produces the following output:

Globally disabling the assessment system needs to be confirmed in System Settings.

 

 

On macOS Sequoia, running the command to enable Gatekeeper produces the following output:

This operation is no longer supported. Please see the man page for more information.

 

In both cases, the Gatekeeper settings in System Settings: Privacy and Security should remain unchanged.

 

Keychain Access app in new location on macOS Sequoia

One of the changes made as part of macOS Sequoia is that the location of Keychain Access.app has moved:

In macOS Sonoma and earlier, Keychain Access.app is in the following location:

/Applications/Utilities/Keychain Access.app

 

As of macOS Sequoia, Keychain Access.app is in the following location:

/System/Library/CoreServices/Applications/Keychain Access.app

Blocking system extension disablement via System Settings on macOS Sequoia

Since the introduction of system extensions in macOS Catalina, Apple has been adding controls for both individual users and Mac admins to allow management of system extensions. For individual users who manage their own Macs without using MDM management, Apple asks the user to approve the system extensions functions. In turn, for managed environments, Apple has provided management profile options to allow Mac admins to pre-approve or block system extensions from running on the Macs in that environment.

As part of the release of macOS Sequoia, Apple has added new user functionality for managing system extensions, as well as management profile options for Mac admins. For more details, please see below the jump.

The new functionality is the ability to manage installed system extensions via System Settings, with the relevant controls being found in System Settings: General: Login Items & Extensions. As an example, let’s look at the LuLu open-source firewall app. This app uses a network extension and a content filter.

On macOS Sonoma, the following management profiles can be deployed to allow LuLu’s network extension to be deployed and removed without the user being prompted.

Profile to allow deployment and removal:


<?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>634D0A81-903D-4639-8C72-39A773AF68A8</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadOrganization</key>
<string>Company Name</string>
<key>PayloadIdentifier</key>
<string>634D0A81-903D-4639-8C72-39A773AF68A8</string>
<key>PayloadDisplayName</key>
<string>LuLu System Extension Allowed and Removable Settings</string>
<key>PayloadDescription</key>
<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>PayloadUUID</key>
<string>FF5677D3-9A0A-4592-8FFE-638846B7F5DD</string>
<key>PayloadType</key>
<string>com.apple.system-extension-policy</string>
<key>PayloadOrganization</key>
<string>Company Name</string>
<key>PayloadIdentifier</key>
<string>FF5677D3-9A0A-4592-8FFE-638846B7F5DD</string>
<key>PayloadDisplayName</key>
<string>System Extensions</string>
<key>PayloadDescription</key>
<string/>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadEnabled</key>
<true/>
<key>AllowUserOverrides</key>
<true/>
<key>AllowedSystemExtensions</key>
<dict>
<key>VBG97UB4TA</key>
<array>
<string>com.objective-see.lulu.extension</string>
</array>
</dict>
<key>RemovableSystemExtensions</key>
<dict>
<key>VBG97UB4TA</key>
<array>
<string>com.objective-see.lulu.extension</string>
</array>
</dict>
</dict>
</array>
</dict>
</plist>

Profile to configure the content filter:


<?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>883C5AE4-FCBA-4A1D-83D5-51120C081063</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadOrganization</key>
<string>Company Name</string>
<key>PayloadIdentifier</key>
<string>883C5AE4-FCBA-4A1D-83D5-51120C081063</string>
<key>PayloadDisplayName</key>
<string>LuLu Content Filter Settings</string>
<key>PayloadDescription</key>
<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>FilterDataProviderBundleIdentifier</key>
<string>com.objective-see.lulu.extension</string>
<key>FilterDataProviderDesignatedRequirement</key>
<string>anchor apple generic and identifier "com.objective-see.lulu.extension" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = VBG97UB4TA)</string>
<key>FilterPacketProviderBundleIdentifier</key>
<string>com.objective-see.lulu.extension</string>
<key>FilterPacketProviderDesignatedRequirement</key>
<string>anchor apple generic and identifier "com.objective-see.lulu.extension" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = VBG97UB4TA)</string>
<key>FilterPackets</key>
<true/>
<key>FilterSockets</key>
<true/>
<key>FilterType</key>
<string>Plugin</string>
<key>PayloadDisplayName</key>
<string>Web Content Filter Payload</string>
<key>PayloadIdentifier</key>
<string>46C79391-5AA6-4A5A-8B91-C298D629F1B7</string>
<key>PayloadOrganization</key>
<string>JAMF Software</string>
<key>PayloadType</key>
<string>com.apple.webcontent-filter</string>
<key>PayloadUUID</key>
<string>46C79391-5AA6-4A5A-8B91-C298D629F1B7</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PluginBundleID</key>
<string>com.objective-see.lulu.app</string>
<key>UserDefinedName</key>
<string>LuLu</string>
</dict>
</array>
</dict>
</plist>

On macOS Sequoia, the new functionality referenced earlier is not managed by either profile. Instead, if you look at System Settings: General: Login Items & Extensions, you should see a Network Extensions entry in the Extensions section.

When you click on the (i) option for the Network Extensions entry, you should see controls like what’s shown below. These controls include the ability to disable the system extension.

To manage this new functionality, Apple has added a new NonRemovableFromUISystemExtensions key to the existing com.apple.system-extension-policy payload for managing system extensions. The following management profile can be deployed to macOS Sequoia Macs to prevent the user from being able to disable the LuLu network extension.


<?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>CC25188C-5988-4EC7-8622-3D7CDD56A6DD</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadOrganization</key>
<string>Company Name</string>
<key>PayloadIdentifier</key>
<string>CC25188C-5988-4EC7-8622-3D7CDD56A6DD</string>
<key>PayloadDisplayName</key>
<string>LuLu System Extension NonRemovableFromUISystemExtensions Settings</string>
<key>PayloadDescription</key>
<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>PayloadUUID</key>
<string>168038F2-B6BA-415E-9FA7-84D45FF1BCAA</string>
<key>PayloadType</key>
<string>com.apple.system-extension-policy</string>
<key>PayloadOrganization</key>
<string>Company Name</string>
<key>PayloadIdentifier</key>
<string>168038F2-B6BA-415E-9FA7-84D45FF1BCAA</string>
<key>PayloadDisplayName</key>
<string>System Extensions</string>
<key>PayloadDescription</key>
<string/>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadEnabled</key>
<true/>
<key>AllowUserOverrides</key>
<true/>
<key>NonRemovableFromUISystemExtensions</key>
<dict>
<key>VBG97UB4TA</key>
<array>
<string>com.objective-see.lulu.extension</string>
</array>
</dict>
</dict>
</array>
</dict>
</plist>

Once deployed, the user controls available via the Network Extensions entry will be grayed out and the user will not be able to change those settings.

Note: Something to be aware of is that the NonRemovableFromUISystemExtensions key only works on macOS Sequoia and older versions of macOS, including macOS Sonoma, will ignore it. This means that deploying profiles which use this NonRemovableFromUISystemExtensions key to older versions of macOS will not result in the new management settings being applied to macOS Sequoia when the Mac is upgraded to run macOS Sequoia. Instead, profiles which use this NonRemovableFromUISystemExtensions key should only be deployed to macOS Sequoia as of this time.

Clearing MDM lock on Apple Silicon Macs when passcode has been lost

One of the functions that Apple’s mobile device management (MDM) has had for years is the ability to send a remote lock command. In this case, an MDM command is sent to a device and a passcode is required in order to clear the lock.

However, sometimes this passcode can get mislaid or forgotten due to insufficient record keeping. In this case, how to clear the lock when you don’t have the passcode? On Apple Silicon Macs, it’s possible to clear the lock but it will require all data on the Mac to be destroyed and the operating system to be reloaded from scratch.

For more details, please see below the jump.

The solution is to put the Apple Silicon Mac into DFU mode and use Apple Configurator running on another Mac to restore the Mac using an IPSW file for the desired operating system. Apple has a KBase article describing how to perform this restoration process available via the link below (follow the Restore procedures):

Once the Mac has been restored, the lock should be cleared and a factory-fresh copy of macOS should be installed.

In the event that this process does not work, the other option available is a logic board hardware repair where the Mac’s existing logic board is exchanged for a new replacement one.

Setting custom variables in AutoPkg using the VariablePlaceholder processor

A while back, I had written a post about using custom variables in an AutoPkg recipe to set version information. Part of my earlier post references using the EndOfCheckPhase processor as a safe way to set variables because the EndOfCheckPhase processor takes no actions. However, even the EndOfCheckPhase processor isn’t completely safe because it does do something even if the processor itself takes no actions and natively sets no output variables. This is because AutoPkg uses it to figure out if it should stop checking for new information as part of a recipe’s run.

To provide a completely safe way to set custom variables in an AutoPkg recipe, I decided to write a VariablePlaceholder AutoPkg processor which truly does absolutely nothing except serve as a convenient way to set custom variables. For more details, please see below the jump.

The VariablePlaceholder processor takes no actions and does nothing. It’s usefulness comes from the fact that AutoPkg will still process Arguments values if they’re defined for the VariablePlaceholder processor. The workflow in this case looks like this:

  1. Add the VariablePlaceholder processor where needed in the AutoPkg recipe.
  2. Perform the desired variable assignment as an Arguments value.

For example, if you need to set the version output variable in an AutoPkg recipe by combining two other output variables (in this example, a major_version variable and a minor_version variable), the VariablePlaceholder processor can be used to do this in a safe way.


<dict>
<key>Processor</key>
<string>VariablePlaceholder</string>
<key>Arguments</key>
<dict>
<key>version</key>
<string>%major_version%.%minor_version%</string>
</dict>
</dict>

view raw

gistfile1.txt

hosted with ❤ by GitHub

A recipe which uses this example setup is shown below:


<?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>Description</key>
<string>Downloads the latest version of Carousel Cloud's screensaver.</string>
<key>Identifier</key>
<string>com.github.rtrouton.download.carouselcloudscreensaver</string>
<key>Input</key>
<dict>
<key>NAME</key>
<string>Carousel</string>
<key>DOWNLOAD_URL</key>
<string>https://carousel-public-files.s3.amazonaws.com/Carousel.Cloud.Screen.Saver.dmg</string&gt;
</dict>
<key>MinimumVersion</key>
<string>1.0.0</string>
<key>Process</key>
<array>
<dict>
<key>Processor</key>
<string>URLDownloader</string>
<key>Arguments</key>
<dict>
<key>url</key>
<string>%DOWNLOAD_URL%</string>
</dict>
</dict>
<dict>
<key>Processor</key>
<string>EndOfCheckPhase</string>
</dict>
<dict>
<key>Arguments</key>
<dict>
<key>info_path</key>
<string>%pathname%/Carousel Cloud.saver/Contents/Info.plist</string>
<key>plist_keys</key>
<dict>
<key>CFBundleVersion</key>
<string>minor_version</string>
<key>CFBundleShortVersionString</key>
<string>major_version</string>
</dict>
</dict>
<key>Processor</key>
<string>PlistReader</string>
</dict>
<dict>
<key>Processor</key>
<string>CodeSignatureVerifier</string>
<key>Arguments</key>
<dict>
<key>input_path</key>
<string>%pathname%/Carousel Cloud.saver</string>
<key>requirement</key>
<string>identifier "com.trms.Carousel-Cloud-Screensaver" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "3WG65JKLQ8"</string>
</dict>
</dict>
<dict>
<key>Processor</key>
<string>com.github.rtrouton.SharedProcessors/VariablePlaceholder</string>
<key>Arguments</key>
<dict>
<key>version</key>
<string>%major_version%.%minor_version%</string>
</dict>
</dict>
<dict>
<key>Processor</key>
<string>EndOfCheckPhase</string>
</dict>
</array>
</dict>
</plist>

The VariablePlaceholder processor is shown below, as well as being available via the following link:

https://github.com/rtrouton/AutoPkg_Processors/tree/main/VariablePlaceholder


#!/usr/local/autopkg/python
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from autopkglib import Processor
__all__ = ["VariablePlaceholder"]
class VariablePlaceholder(Processor):
"""This processor performs no action, but serves as a place to set variables for use by AutoPkg recipes."""
input_variables = {}
output_variables = {}
description = __doc__
def main(self):
return
if __name__ == "__main__":
PROCESSOR = VariablePlaceholder()
PROCESSOR.execute_shell()

If you want to use the VariablePlaceholder processor hosted from my AutoPkg recipe repo, first verify that AutoPkg is installed on the Mac you’re using. Once verified, run the following command:


autopkg repo-add rtrouton-recipes

view raw

gistfile1.txt

hosted with ❤ by GitHub