09-17-2023 07:14 PM - edited 04-29-2024 01:22 AM
If the Apple Silicon Mac computers can not be enrolled through PreStage Enrollment, the Recovery Lock can only be set from remote command, here's how to do it in Jamf API.
1. First, we need to know the managementId of the target computer. we can copy it from computer‘s inventory. (Jamf Pro Server 10.50.0 and above)
2. Open the Jamf Pro Server API page (https://yourInstance.jamfcloud.com/api, or https://yourjss.domain.com:8443/api) , and choose Jamf Pro API PRODUCTION View.
3. Navigate to "MDM", and choose "POST /v2/mdm/commands", then click "Try it now".
4. Modify the request json body. Input the managementId and the recovery lock password. Set to empty ("") will remove the recovery lock password, set to a different one from previous can RESET the existed recovery lock passcode directly. (PS: Firmware/EFI passcode cannot be reset, must be removed and then set a new passcode.)
(copy paste and modify it)
{
"clientData": [
{
"managementId": "6c1c6fdd-999d-4801-ab61-8b1785cfa129",
"clientType": "COMPUTER"
}
],
"commandData": {
"commandType": "SET_RECOVERY_LOCK",
"newPassword": "12345678"
}
}
4. Click Execute.
5. The SetRecoveryLock command status can be found in computer's Management History.
That's all steps to set recovery lock passcode for one Apple Silicon computers in Jamf Pro API page. We can also set it for all Apple Silicon computers from a script.
Posted on 09-18-2023 06:27 AM
thanks for the tip that was decent. i recently had to tackle this issue for a dozen or so laptops that were enrolled prior to 11.5 of macOS; i ended up using the following script, it worked well enough after the dependencies wqere installed on the mac.
#!/usr/bin/env python3
####
#
# SetRecoveryLockJAMF.py
#
#
#
# Script to set recovery lock for macOS computers in JAMF Pro
# Requires:
# Python3
# Python module: requests (can be installed by running 'python3 -m pip install requests')
#
# Adapted from https://github.com/shbedev/jamf-recovery-lock
#
####
### User-edited Variables ###
# Define how we connect to JAMF
jamf_url = 'jamf.myorg.edu:8443'
jamf_api_username = 'jamf_api'
jamf_api_password = 'ApiPasswordGoesHere'
########## DO NOT EDIT BELOW THIS LINE ##########
import argparse, sys
# Initialize command line argument parser
parser = argparse.ArgumentParser()
# Adding command line arguments
parser.add_argument(
"SearchString",
help = "String to use to search JAMF computer names"
)
group = parser.add_mutually_exclusive_group()
group.add_argument(
"-p", "--Passcode",
help = "Specify Recovery Lock passcode (default is blank)"
)
group.add_argument(
"-r", "--RandomPasscode", nargs='?', const=20,
help = "Generate a different random Recovery Lock passcode for each computer (default length is 20, specify a value for a different length)"
)
# Read arguments from command line
args = parser.parse_args()
### From git project auth/basic_auth.py
import base64
def auth_token():
# create base64 encoded string of jamf API user credetinals
credentials_str = f'{jamf_api_username}:{jamf_api_password}'
data_bytes = credentials_str.encode("utf-8")
encoded_bytes = base64.b64encode(data_bytes)
encoded_str = encoded_bytes.decode("utf-8")
return encoded_str
### From git project auth/bearer_auth.py
import os, time, requests
# current working directory
cwd = os.path.dirname(os.path.realpath(__file__))
# path of token file in current working directory
token_file = f'{cwd}/token.txt'
def request_token():
"""Generate an auth token from API"""
headers = {
'Accept': 'application/json',
'Authorization': f'Basic {auth_token()}',
'Content-Type': 'application/json',
}
try:
response = requests.request("POST", f'https://{jamf_url}/api/v1/auth/token', headers=headers)
response.raise_for_status()
except requests.exceptions.HTTPError as err:
raise SystemExit(err)
return response.json()['token']
def get_token():
"""Returns a token from local cache or API request"""
current_time = int(time.time())
# check if token is cached and if it is less than 30 minutes old
if os.path.exists(token_file) and ((current_time - 1800) < os.stat(token_file)[-1]):
# return a cached token from file
return read_token_from_local()
else:
# return a token from API
return get_token_from_api()
def get_token_from_api():
"""Returns a token from an API request"""
token = request_token()
cache_token(token)
return token
def cache_token(token):
"""
Cache token to local file
Parameters:
token - str
"""
with open(token_file, 'w') as file_obj:
file_obj.write(token)
def read_token_from_local():
"""Read cached token from local file"""
with open(token_file, 'r') as file_obj:
token = file_obj.read().strip()
return token
### From git project computers.py
import requests
import math
headers = {
'Accept': 'application/json',
'Authorization': f'Bearer {get_token()}',
'Content-Type': 'application/json',
}
def get_computer_count():
"""
Returns the number of computers in Jamf Pro
"""
try:
response = requests.get(
url=f'https://{jamf_url}/api/v1/computers-inventory?section=HARDWARE&page=0&page-size=1&sort=id%3Aasc',
headers=headers
)
response.raise_for_status()
except requests.exceptions.HTTPError as err:
raise SystemExit(err)
count = response.json()['totalCount']
#print("Number of computers in JAMF = " + str(count))
return count
computers_per_page = 1000
number_of_pages = math.ceil(get_computer_count() / computers_per_page)
def get_arm64(filter = None):
"""
Returns Jamf IDs of all arm64 type computers
Parameters:
filter - (e.g. 'filter=general.name=="jdoe-mbp"'). If empty, returns all computers.
Computer name in filter is not case sensitive
"""
computers_id = []
for pageIndex in range(number_of_pages):
try:
response = requests.get(
url=f'https://{jamf_url}/api/v1/computers-inventory?section=HARDWARE&page={pageIndex}&page-size={computers_per_page}&sort=id%3Aasc&{filter}',
headers=headers
)
response.raise_for_status()
except requests.exceptions.HTTPError as err:
raise SystemExit(err)
computers = response.json()['results']
for computer in computers:
if computer['hardware']['processorArchitecture'] == 'arm64':
computers_id.append(computer['id'])
if computers_id == []:
sys.exit("No Apple Silicon computers found in Jamf that match search string.")
return computers_id
def get_mgmt_id(computers_id):
"""
Returns Jamf computers management id
Parameters:
computers_id - (e.g. ['10', '12']]). List of Jamf computers id
"""
computers_mgmt_id = []
for pageIndex in range(number_of_pages):
try:
response = requests.get(
url = f'https://{jamf_url}/api/preview/computers?page={pageIndex}&page-size={computers_per_page}&sort=name%3Aasc',
headers=headers
)
response.raise_for_status()
except requests.exceptions.HTTPError as err:
raise SystemExit(err)
computers = response.json()['results']
for computer_id in computers_id:
for computer in computers:
# Find computers that given computer id in list of computers
if computer['id'] == computer_id:
computer_mgmt_id = computer['managementId']
computer_name = computer['name']
# Add computer to list
computers_mgmt_id.append({
'id': computer_id,
'name': computer_name,
'mgmt_id': computer_mgmt_id
})
break
return computers_mgmt_id
### From git project recovery_lock.py
import requests
headers = {
'Accept': 'application/json',
'Authorization': f'Bearer {get_token()}',
'Content-Type': 'application/json',
}
def set_key(computer_name, management_id, recovery_lock_key):
"""Sets a Recovery Lock key for a given computer"""
print(f'Settings recovery lock key: {recovery_lock_key} for {computer_name}')
payload = {
'clientData': [
{
'managementId': f'{management_id}',
'clientType': 'COMPUTER'
}
],
'commandData': {
'commandType': 'SET_RECOVERY_LOCK',
'newPassword': f'{recovery_lock_key}'
}
}
try:
response = requests.request("POST", f'https://{jamf_url}/api/preview/mdm/commands', headers=headers, json=payload)
response.raise_for_status()
except requests.exceptions.HTTPError as err:
raise SystemExit(err)
### From git project main.py
from random import randint
computers_id = get_arm64('filter=general.name=="*'+args.SearchString+'*"')
computers_mgmt_id = get_mgmt_id(computers_id)
print("These are the changes you will be making:")
for computer in computers_mgmt_id:
computer_name = computer['name']
computer_mgmt_id = computer['mgmt_id']
if args.Passcode:
print(" "+computer_name+" will have its Recovery Lock set to "+args.Passcode)
elif args.RandomPasscode:
print(" "+computer_name+" will have its Recovery Lock set to a random "+str(args.RandomPasscode)+"-digit number")
else:
print(" "+computer_name+" will have its Recovery Lock cleared")
print("")
go_ahead = input("Do you wish to proceed? (y/n)")
print("")
if go_ahead == ("y" or "Y"):
for computer in computers_mgmt_id:
computer_name = computer['name']
computer_mgmt_id = computer['mgmt_id']
if args.Passcode:
recovery_lock_key = args.Passcode
print(" Command sent for "+computer_name+" Recovery Lock to be set to "+str(recovery_lock_key))
elif args.RandomPasscode:
rand_low_val=pow(10,(int(args.RandomPasscode) - 1))
rand_val_high=pow(10,int(args.RandomPasscode)) - 1
recovery_lock_key = randint(rand_low_val,rand_val_high)
print(" Command sent for "+computer_name+" Recovery Lock to be set to "+str(recovery_lock_key))
else:
recovery_lock_key = ''
print(" Command sent to clear Recovery Lock on "+computer_name)
set_key(computer_name, computer_mgmt_id, recovery_lock_key)
print("")
Posted on 09-20-2023 03:41 AM
Cool, great to see that there is a batch operation script.
Posted on 10-31-2023 03:57 PM
Am I correct in that if this is in a policy scoped to devices without a recovery password set, it will assign a random key to each individually? Or is it designed to be run outside of Jamf and will pick out those devices needing a password and assign? Sorry for the noob questions - this looks really useful but I'm barely literate in python at this point. Was also wondering if you could repost with proper code formatting - was running it through an interpreter and it appears to be missing all its necessary indents. thanks much
Posted on 10-31-2023 08:14 PM
Sure. As best I can recall in order to use the script; python3 and the associated dependencies mentioned in the script must be installed on whatever mac you are using ot administer your jamf instance. It can be on any network as long as it has an internet connection (assuming the instance is jamf cloud based), as it just needs to auth to your jamf instance and use some API calls.
After the dependencies have been installed and the script customized with your FQDN and API user; you can execute the script from the terminal as follows:
python3 SetRecoveryLockJAMF.py Tad -r
The above command would loop through your Macs and set a random recovery password, for the first computer it found with Tad in the name. Repeat the process as necessary, replacing the name to hit all the macs you need. Hope that makes sense. Here is the script in a code block:
#!/usr/bin/env python3
####
#
# SetRecoveryLockJAMF.py
#
#
# Script to set recovery lock for macOS computers in JAMF Pro
# Requires:
# Python3
# Python module: requests (can be installed by running 'python3 -m pip install requests')
#
# Adapted from https://github.com/shbedev/jamf-recovery-lock
#
####
### User-edited Variables ###
# Define how we connect to JAMF
jamf_url = ''
jamf_api_username = ''
jamf_api_password = ''
########## DO NOT EDIT BELOW THIS LINE ##########
import argparse, sys
# Initialize command line argument parser
parser = argparse.ArgumentParser()
# Adding command line arguments
parser.add_argument(
"SearchString",
help = "String to use to search JAMF computer names"
)
group = parser.add_mutually_exclusive_group()
group.add_argument(
"-p", "--Passcode",
help = "Specify Recovery Lock passcode (default is blank)"
)
group.add_argument(
"-r", "--RandomPasscode", nargs='?', const=20,
help = "Generate a different random Recovery Lock passcode for each computer (default length is 20, specify a value for a different length)"
)
# Read arguments from command line
args = parser.parse_args()
### From git project auth/basic_auth.py
import base64
def auth_token():
# create base64 encoded string of jamf API user credetinals
credentials_str = f'{jamf_api_username}:{jamf_api_password}'
data_bytes = credentials_str.encode("utf-8")
encoded_bytes = base64.b64encode(data_bytes)
encoded_str = encoded_bytes.decode("utf-8")
return encoded_str
### From git project auth/bearer_auth.py
import os, time, requests
# current working directory
cwd = os.path.dirname(os.path.realpath(__file__))
# path of token file in current working directory
token_file = f'{cwd}/token.txt'
def request_token():
"""Generate an auth token from API"""
headers = {
'Accept': 'application/json',
'Authorization': f'Basic {auth_token()}',
'Content-Type': 'application/json',
}
try:
response = requests.request("POST", f'https://{jamf_url}/api/v1/auth/token', headers=headers)
response.raise_for_status()
except requests.exceptions.HTTPError as err:
raise SystemExit(err)
return response.json()['token']
def get_token():
"""Returns a token from local cache or API request"""
current_time = int(time.time())
# check if token is cached and if it is less than 30 minutes old
if os.path.exists(token_file) and ((current_time - 1800) < os.stat(token_file)[-1]):
# return a cached token from file
return read_token_from_local()
else:
# return a token from API
return get_token_from_api()
def get_token_from_api():
"""Returns a token from an API request"""
token = request_token()
cache_token(token)
return token
def cache_token(token):
"""
Cache token to local file
Parameters:
token - str
"""
with open(token_file, 'w') as file_obj:
file_obj.write(token)
def read_token_from_local():
"""Read cached token from local file"""
with open(token_file, 'r') as file_obj:
token = file_obj.read().strip()
return token
### From git project computers.py
import requests
import math
headers = {
'Accept': 'application/json',
'Authorization': f'Bearer {get_token()}',
'Content-Type': 'application/json',
}
def get_computer_count():
"""
Returns the number of computers in Jamf Pro
"""
try:
response = requests.get(
url=f'https://{jamf_url}/api/v1/computers-inventory?section=HARDWARE&page=0&page-size=1&sort=id%3Aasc',
headers=headers
)
response.raise_for_status()
except requests.exceptions.HTTPError as err:
raise SystemExit(err)
count = response.json()['totalCount']
#print("Number of computers in JAMF = " + str(count))
return count
computers_per_page = 1000
number_of_pages = math.ceil(get_computer_count() / computers_per_page)
def get_arm64(filter = None):
"""
Returns Jamf IDs of all arm64 type computers
Parameters:
filter - (e.g. 'filter=general.name=="jdoe-mbp"'). If empty, returns all computers.
Computer name in filter is not case sensitive
"""
computers_id = []
for pageIndex in range(number_of_pages):
try:
response = requests.get(
url=f'https://{jamf_url}/api/v1/computers-inventory?section=HARDWARE&page={pageIndex}&page-size={computers_per_page}&sort=id%3Aasc&{filter}',
headers=headers
)
response.raise_for_status()
except requests.exceptions.HTTPError as err:
raise SystemExit(err)
computers = response.json()['results']
for computer in computers:
if computer['hardware']['processorArchitecture'] == 'arm64':
computers_id.append(computer['id'])
if computers_id == []:
sys.exit("No Apple Silicon computers found in Jamf that match search string.")
return computers_id
def get_mgmt_id(computers_id):
"""
Returns Jamf computers management id
Parameters:
computers_id - (e.g. ['10', '12']]). List of Jamf computers id
"""
computers_mgmt_id = []
for pageIndex in range(number_of_pages):
try:
response = requests.get(
url = f'https://{jamf_url}/api/preview/computers?page={pageIndex}&page-size={computers_per_page}&sort=name%3Aasc',
headers=headers
)
response.raise_for_status()
except requests.exceptions.HTTPError as err:
raise SystemExit(err)
computers = response.json()['results']
for computer_id in computers_id:
for computer in computers:
# Find computers that given computer id in list of computers
if computer['id'] == computer_id:
computer_mgmt_id = computer['managementId']
computer_name = computer['name']
# Add computer to list
computers_mgmt_id.append({
'id': computer_id,
'name': computer_name,
'mgmt_id': computer_mgmt_id
})
break
return computers_mgmt_id
### From git project recovery_lock.py
import requests
headers = {
'Accept': 'application/json',
'Authorization': f'Bearer {get_token()}',
'Content-Type': 'application/json',
}
def set_key(computer_name, management_id, recovery_lock_key):
"""Sets a Recovery Lock key for a given computer"""
print(f'Settings recovery lock key: {recovery_lock_key} for {computer_name}')
payload = {
'clientData': [
{
'managementId': f'{management_id}',
'clientType': 'COMPUTER'
}
],
'commandData': {
'commandType': 'SET_RECOVERY_LOCK',
'newPassword': f'{recovery_lock_key}'
}
}
try:
response = requests.request("POST", f'https://{jamf_url}/api/preview/mdm/commands', headers=headers, json=payload)
response.raise_for_status()
except requests.exceptions.HTTPError as err:
raise SystemExit(err)
### From git project main.py
from random import randint
computers_id = get_arm64('filter=general.name=="*'+args.SearchString+'*"')
computers_mgmt_id = get_mgmt_id(computers_id)
print("These are the changes you will be making:")
for computer in computers_mgmt_id:
computer_name = computer['name']
computer_mgmt_id = computer['mgmt_id']
if args.Passcode:
print(" "+computer_name+" will have its Recovery Lock set to "+args.Passcode)
elif args.RandomPasscode:
print(" "+computer_name+" will have its Recovery Lock set to a random "+str(args.RandomPasscode)+"-digit number")
else:
print(" "+computer_name+" will have its Recovery Lock cleared")
print("")
go_ahead = input("Do you wish to proceed? (y/n)")
print("")
if go_ahead == ("y" or "Y"):
for computer in computers_mgmt_id:
computer_name = computer['name']
computer_mgmt_id = computer['mgmt_id']
if args.Passcode:
recovery_lock_key = args.Passcode
print(" Command sent for "+computer_name+" Recovery Lock to be set to "+str(recovery_lock_key))
elif args.RandomPasscode:
rand_low_val=pow(10,(int(args.RandomPasscode) - 1))
rand_val_high=pow(10,int(args.RandomPasscode)) - 1
recovery_lock_key = randint(rand_low_val,rand_val_high)
print(" Command sent for "+computer_name+" Recovery Lock to be set to "+str(recovery_lock_key))
else:
recovery_lock_key = ''
print(" Command sent to clear Recovery Lock on "+computer_name)
set_key(computer_name, computer_mgmt_id, recovery_lock_key)
print("")
Posted on 11-01-2023 10:00 AM
Fantastic - thanks much! I did manage to fix most of the formatting issues but was still having a little trouble - found I also had to update the fprime-tools ( python3 -m pip install fprime-tools ) and was getting auth errors - looks like the auth issue was because I had the search term last instead of first...
If I can eventually grok enough python, the only change I might make is to make it operate on a list of serials but still this is a really wonderful adaptation of shbedev's script bundle.
Kudos!
Posted on 11-08-2023 06:41 AM
A bash script share to you, you can edit and run it in Terminal on your Mac computer.
#!/bin/bash
# created by Steven Xu (xuhao@solutionkeys.com)
# https://developer.jamf.com/jamf-pro/docs/jamf-pro-api-overview
# your jss url
jss_url="https://xxx.jamfcloud.com"
# Computer Group ID of Apple Silicon Macs which need to set the new Recovery Lock passcode.
cgroup_id="121"
# Recovery Lock passcode, set the value to empty("") means remove the passcode.
passcode="123456"
############################### don't edit #######################
read -p "Your jamf pro ($jss_url) account username: " api_user
stty -echo
read -p "Your password: " api_pass
stty -echo
echo
authToken=$(curl -s -u "${api_user}:${api_pass}" "$jss_url/api/v1/auth/token" -X POST -H "accept: application/json" )
bearerToken=$(echo "$authToken" | plutil -extract token raw -)
# Get computer IDs from the group.
ids=$(curl -ksu "$api_user:$api_pass" -X GET "$jss_url/JSSResource/computergroups/id/$cgroup_id" -H "accept: application/xml" \
| xmllint --format --xpath /computer_group/computers/computer/id - | sed 's/<id>//g' | sed 's/<\/id>/,/g' | rev | sed 's/,//' | rev)
if [ -z "$ids" ]; then
echo "no devices in this group"
exit 0
else
echo "Computer IDs in this group: $ids"
fi
# Send command to set recovery lock password
for id in $ids
do
echo "Sending command to computer $id to set the recovery lock passcode..."
management_id=$(curl -s -H "Authorization: Bearer ${bearerToken}" "$jss_url/api/v1/computers-inventory/$id?section=GENERAL" -X GET -H "Content-type: application/json" | grep managementId | awk -F '"' '{printf $4}')
curl --location --request POST "${jss_url}/api/preview/mdm/commands" \
--header "Authorization: Bearer ${bearerToken}" \
--header 'Content-Type: application/json' \
--data-raw '{
"clientData": [
{
"managementId": "'$management_id'",
"clientType": "COMPUTER"
}
],
"commandData": {
"commandType": "SET_RECOVERY_LOCK",
"newPassword": "'$passcode'"
}
}'
echo "\n"
done
# Invalidate Bearer Token
echo "Invalidate the Token"
curl -s -H "Authorization: Bearer ${bearerToken}" "$jss_url/api/v1/auth/invalidate-token" -X POST -w "%{http_code}" -o /dev/null
echo "Done, Recovery Lock commands sent."
Posted on 11-09-2023 12:29 PM
Thanks @Steven_Xu - yes, that was going to be my alternate approach - convert to a shell script. I'd probably make it a deployable script that would run automatically when a device is part of the no_recovery_lock group.
Posted on 11-29-2023 04:06 AM
@Steven_Xu Getting an error on below lines
"ids=$(curl -ksu "$api_user:$api_pass" -X GET "$jss_url/JSSResource/computergroups/id/$cgroup_id" -H "accept: application/xml" \ | xmllint --format --xpath /computer_group/computers/computer/id - | sed 's/<id>//g' | sed 's/<\/id>/,/g' | rev | sed 's/,//' | rev) "
Posted on 12-03-2023 05:51 PM
you may need to enable "Allow Basic authentication for the Classic API" in Jamf Pro password policy settings. BUT the basic authentication will be deprecated soon, bearer token authentication is recommend.
Posted on 02-05-2024 03:42 PM
Finally got a chance to revisit this and wanted to share: I came up with a group/policy workflow that runs automatically if Macs somehow manage to go through PreStage enrollment without Recovery Lock being enabled.
First step is the script (full credit to DerFlounder for his API token sequence):
#! /usr/bin/env zsh
# Set default exit code
exitCode=0
# Explicitly set initial value for the api_token variable to null:
api_token=""
# Explicitly set initial value for the token_expiration variable to null:
token_expiration=""
## generate random 39-digit numerical passcode
recPasscode=$(jot -r -s '' 39 0 9)
## Get the Mac's UUID string
UUID=$(ioreg -rd1 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformUUID/{print $4}')
# Set basic api access info
jamfpro_url="https://myjssserver.jamfcloud.com"
# Use the encoded credentials with Basic Authorization to request a bearer token
authToken=$(/usr/bin/curl "${jamfpro_url}/api/v1/auth/token" --silent --request POST --header "accept: application/json" --header "Authorization: Basic $4")
# Parse the returned output for the bearer token and store the bearer token as a variable.
if [[ $(/usr/bin/sw_vers -productVersion | awk -F . '{print $1}') -lt 12 ]]; then
api_token=$(/usr/bin/awk -F \" 'NR==2{print $4}' <<< "$authToken" | /usr/bin/xargs)
else
api_token=$(/usr/bin/plutil -extract token raw -o - - <<< "$authToken")
fi
# 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}")
## Pull JSSID from "general" subsection
JSS_ID=$(curl -H "Accept: text/xml" -H "Authorization: Bearer ${api_token}" -sf "${jamfpro_url}/JSSResource/computers/udid/${UUID}/subset/general" | xmllint --xpath '/computer/general/id/text()' -)
## get mgmtID
mgmtID=$(curl -s -X GET "${jamfpro_url}/api/v1/computers-inventory-detail/${JSS_ID}" -H "accept: application/json" -H "Authorization: Bearer ${api_token}" | grep -i "managementid" | awk -F '"' '{print $4}')
#checkpoints for troubleshooting
#echo $api_token
#echo $api_authentication_check
#echo $JSS_ID
#echo $mgmtID
## set Recovery Lock password
curl --location --request POST "${jamfpro_url}/api/preview/mdm/commands" \
--header "Authorization: Bearer ${api_token}" \
--header 'Content-Type: application/json' \
--data-raw '{
"clientData": [
{
"managementId": "'$mgmtID'",
"clientType": "COMPUTER"
}
],
"commandData": {
"commandType": "SET_RECOVERY_LOCK",
"newPassword": "'$recPasscode'"
}
}'
## tag Mac as Recovery Lock set by API
if [[ ! -f "/usr/local/mylocalorgdir/RLapiSet" ]] && [[ $? == "0" ]]; then
touch /usr/local/mylocalorgdir/RLapiSet
fi
Note that per the bearer token request, I'm using script parameters to pass along the encoded API creds.
2nd step is to create an extension attribute for the last section of the script so that computers can be detected in a smart group as having had the script's policy run:
#! /usr/bin/env zsh
if [[ -f "/usr/local/mylocalorgdir/RLapiSet" ]]; then
Result="Yes"
else
Result="No"
fi
echo "<result>$Result</result>"
Then create two smart groups - one for devices that don't have Recovery Lock enabled:
and one for those that have completed the script policy:
Finally, this will require two policies; one Ongoing policy triggered by Login and a Custom Event and scoped to both Smart Groups above:
And one triggered by check-in which runs the policy trigger for the other policy and is scoped only to the Recovery Lock not enabled group:
Appears to work beautifully - also created another smart group for Macs that might end up members of both of the above groups in order to detect ones that have had the policies run but for whatever reason haven't actually had the set Recovery Lock mgmt cmd go through.
Posted on 02-27-2024 07:00 PM
Wow, wonderful!! thanks for sharing.
Posted on 04-24-2024 07:10 AM
I followed your guide but I'm having trouble getting it to work. The policy with the script runs successfully, but the recovery lock isn't active. The log says: "httpStatus" : 401.
I'm not very familiar with the API, so I'm not sure which permissions are necessary for the API role and how to handle the credentials. I've already created an API role with client-id and secret, but I'm not sure if that's the right approach. Could you give me an example of how you do that?
Thanks!
Posted on 04-24-2024 08:04 AM
401 means unauthorized, try to add "View MDM command information in Jamf Pro API" permission to your API Role.
Posted on 04-24-2024 11:59 PM
I tried it (also with some other permissions), but it does not help.
Posted on 04-25-2024 10:01 AM
@Mideto Admittedly not best practice, but I just use a full admin account for API calls - in general I think the obfuscation provided by the script parameters is pretty good. As far as I know the only other ones you might need is the Send Set Recovery Lock Command in the Server Actions section and any of the view settings, so that you can get the JSS & MGMT IDs, as well as the . If you enable the echo cmds under the checkpoints comment, you should be able to tell which API calls aren't working.