Menu Close

View recoveryOS Password via Self Service

TL; DR: A missing SetRecoveryLock - Scheduled Jamf Pro API command has put this workflow on pause; please upvote JN-I-25769:

“We’ve successfully deployed a proof-of-concept script to display a Mac’s Recovery Lock password to the end-user via the Jamf Pro API.

“Now, we’d like to simulate the SetRecoveryLock - Scheduled MDM command via the Jamf Pro API which automatically occurs when the Recovery Lock Password is viewed in the Jamf Pro Computer Record.”

View recoveryOS Password Jamf Pro Self Service Policy

Background

When Jamf Pro 10.32 introduced Recovery Lock Enablement in macOS Using the Jamf Pro API, I was excited to try the other side of the coin: Allowing end-users to view the recoveryOS password for their Mac with Apple silicon and then programmatically generating a new, random Recovery Lock password.

The Execution Frequency of the Self Service policy would be limited to once every day (or week, or month), and we could use the policy logs for auditing purposes.

Configuration

Important: For security reasons, Jamf does not recommend running scripts that contain user or API credentials from managed computers

Jamf Product Documentation

User Account

Using Creating a Jamf Pro User Account as a guide, create a dedicated Jamf Pro User Account to view a computer’s Recovery Lock password with the following privileges:

  • Jamf Pro Server Objects
    • Computers: Read
  • Jamf Pro Server Actions
    • View Recovery Lock: Enabled

Script

After reviewing Rich Trouton’s Jamf Pro API Bearer Token post, this seemed like the perfect time to make the transition to leveraging Bearer Tokens.

(Be certain to first encrypt the credentials of the Jamf Pro User account.)

Parameter Labels

  • Parameter 4: Authorization Key
  • Parameter 5: API Username (Read-only)
  • Parameter 6: API Password (encrypted)
#!/bin/bash

####################################################################################################
#
#   recoveryOS Password View
#
#   Purpose: Displays the computer's Recovery Lock Password to the end-user and then generates
#   a new, random Recovery Lock password via `SetRecoveryLock - Scheduled`
#
####################################################################################################
#
# HISTORY
#
#   Version 0.0.1, 04-Feb-2022, Dan K. Snelson (@dan-snelson)
#       Original version
#
####################################################################################################



####################################################################################################
#
# Variables
#
####################################################################################################

scriptVersion="0.0.1"
scriptResult="Executing v${scriptVersion}; "
apiBearerToken=""
authorizationKey="${4}"
apiUsername="${5}"
apiPasswordEncrypted="${6}"
Salt="Salt_Goes_Here"
Passphrase="Passphrase_Goes_Here"
apiURL=$(/usr/bin/defaults read /Library/Preferences/com.jamfsoftware.jamf.plist jss_url)
computerUUID=$(/usr/sbin/ioreg -d2 -c IOPlatformExpertDevice | /usr/bin/awk -F\" '/IOPlatformUUID/{print $(NF-1)}')
osProductVersion=$(/usr/bin/sw_vers -productVersion | /usr/bin/awk -F . '{print $1}')



####################################################################################################
#
# Functions
#
####################################################################################################

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check for a specified value in Parameter 4 to prevent unauthorized script execution
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function authorizationCheck() {

    if [[ "${authorizationKey}" != "PurpleMonkeyDishwasher" ]]; then

        scriptResult+="Error: Incorrect Authorization Key; exiting."
        echo "${scriptResult}"
        exit 1

    else

        scriptResult+="Correct Authorization Key, proceeding; "

    fi

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Decrypt Password
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function decryptPassword() {
    /bin/echo "${1}" | /usr/bin/openssl enc -aes256 -md sha256 -d -a -A -S "${2}" -k "${3}"
}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Display User Message
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function displayUserMessage() {
    /usr/local/jamf/bin/jamf displayMessage -message "${1}" &
}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Parse JSON string ($1) and return the desired key ($2)
# https://paulgalow.com/how-to-work-with-json-api-data-in-macos-shell-scripts
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function getJsonValue() {
    JSON="$1" /usr/bin/osascript -l 'JavaScript' \
        -e 'const env = $.NSProcessInfo.processInfo.environment.objectForKey("JSON").js' \
        -e "JSON.parse(env).$2"
}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Obtain Jamf Pro Bearer Token via Basic Authentication
# https://derflounder.wordpress.com/2022/01/05/
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function obtainJamfProAPIBearerToken() {

    if [[ ${osProductVersion} -lt 12 ]]; then
        apiBearerToken=$(/usr/bin/curl -X POST --silent -u "${apiUsername}:${apiPassword}" "${apiURL}/api/v1/auth/token" | /usr/bin/python -c 'import sys, json; print json.load(sys.stdin)["token"]')
    else
        apiBearerToken=$(/usr/bin/curl -X POST --silent -u "${apiUsername}:${apiPassword}" "${apiURL}/api/v1/auth/token" | /usr/bin/plutil -extract token raw -)
    fi

    # scriptResult+="apiBearerToken: ${apiBearerToken}; "

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Verify API authentication is using a valid Bearer Token; returns the HTTP status code
# https://derflounder.wordpress.com/2022/01/05/
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function validateJamfProAPIBearerToken() {

    apiBearerTokenCheck=$(/usr/bin/curl --write-out %{http_code} --silent --output /dev/null "${apiURL}/api/v1/auth" --request GET --header "Authorization: Bearer ${apiBearerToken}")

    scriptResult+="apiBearerTokenCheck: ${apiBearerTokenCheck}; "

    if [[ ${apiBearerTokenCheck} != 200 ]]; then

        scriptResult+="Error: ${apiBearerTokenCheck}; exiting."
        echo "${scriptResult}"
        exit 1

    fi

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Validate and optionally renew the Bearer Token; returns the HTTP status code
# https://derflounder.wordpress.com/2022/01/05/
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function validateRenewBearerToken() {

    validateJamfProAPIBearerToken

    if [[ ${apiBearerTokenCheck} == 200 ]]; then

        if [[ ${osProductVersion} -lt 12 ]]; then
            apiBearerToken=$(/usr/bin/curl "${apiURL}/api/v1/auth/keep-alive" --silent --request POST --header "Authorization: Bearer ${apiBearerToken}" | python -c 'import sys, json; print json.load(sys.stdin)["token"]')
        else
            apiBearerToken=$(/usr/bin/curl "${apiURL}/api/v1/auth/keep-alive" --silent --request POST --header "Authorization: Bearer ${apiBearerToken}" | plutil -extract token raw -)
        fi

        scriptResult+="Renewed Bearer Token: ${apiBearerToken}; "

    else

        scriptResult+="Expired Bearer Token; renewing …"

        obtainJamfProAPIBearerToken

    fi

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Invalidate the Bearer Token
# https://derflounder.wordpress.com/2022/01/05/
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function invalidateJamfProAPIBearerToken() {

    validateJamfProAPIBearerToken

    if [[ ${apiBearerTokenCheck} == 200 ]]; then

        scriptResult+="Bearer Token still valid; invalidate; "

        apiBearerToken=$(/usr/bin/curl "${apiURL}/api/v1/auth/invalidate-token" --silent --header "Authorization: Bearer ${apiBearerToken}" -X POST)
        apiBearerToken=""

        scriptResult+="Bearer Token invalidated; "

    else

        scriptResult+="Bearer Token already expired; "

    fi

}



####################################################################################################
#
# Program
#
####################################################################################################

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Logging preamble
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

scriptResult+="RecoveryOS Password View (${scriptVersion})"



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Confirm Authorization
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

authorizationCheck



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Decrypt API Password
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

apiPassword=$(decryptPassword ${apiPasswordEncrypted} ${Salt} ${Passphrase})



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Obtain and validate Jamf Pro API Bearer Token
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

obtainJamfProAPIBearerToken

validateJamfProAPIBearerToken



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Determine computer's Jamf Pro Computer ID
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

computerRecord=$(/usr/bin/curl -s "${apiURL}api/v1/computers-inventory?section=GENERAL&filter=udid%3D%3D%22${computerUUID}%22" -H "Authorization: Bearer ${apiBearerToken}")

jssID=$(getJsonValue "$computerRecord" 'results[0].id')

if [[ -z ${jssID} ]]; then

    scriptResult+="Error: Unable to determine jssID; exiting."
    echo "${scriptResult}"
    exit 1

else

    # scriptResult+="jssID: ${jssID}; "
    scriptResult+="Determined computer's Jamf Pro Computer ID; proceeding; "

fi



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Determine and display the computer's recoveryOS Password
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

computerRecoveryOSPasswordRaw=$(/usr/bin/curl -s "${apiURL}api/v1/computers-inventory/${jssID}/view-recovery-lock-password" -H "Authorization: Bearer ${apiBearerToken}")

computerRecoveryOSPassword=$(getJsonValue "$computerRecoveryOSPasswordRaw" 'recoveryLockPassword')

computerRecoveryOSPasswordHumanRedable=$(echo ${computerRecoveryOSPassword} | sed 's/.\{4\}/& /g')

# scriptResult+="recoveryOS Password: ${computerRecoveryOSPasswordHumanRedable}; "

displayUserMessage "recoveryOS Password:

${computerRecoveryOSPasswordHumanRedable}"



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Generate a new, random Recovery Lock password
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

# The `SetRecoveryLock - Scheduled` Jamf Pro API Command is not currently available
# Please upvote JN-I-25769
# https://ideas.jamf.com/ideas/JN-I-25769



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Exit
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

invalidateJamfProAPIBearerToken

scriptResult+="Goodbye!"

echo "${scriptResult}"

exit 0

Policy

General

  • Display Name: recoveryOS Password View (0.0.1)
  • Trigger: None
  • Execution Frequency: Once every day

Scripts

  • See Above

Self Service

  • Make the policy available in Self Service: Enabled

Closed Jamf Support Case; Opened Feature Request

After confirming with Jamf Support that this works as expected when viewing a computer’s Inventory > Security > Recovery Lock via the Jamf Pro GUI, but SetRecoveryLock - Scheduled wasn’t currently available via the Jamf Pro API, I opened Feature Request JN-I-25769.

Posted in API, Jamf Pro, Scripts

Related Posts