Sonoma 14.2+ Secure Token / Volume Ownership / Boostrap Token issue?

whiteb
Contributor II

I have a Smart Group monitoring computers where the bootstrap token isn't escrowed. In the past week, I noticed a couple freshly wiped + re-enrolled computers (all on Sonoma 14.2 and 14.2.1), where the first user logging into the computer isn't getting the Secure Token or Volume Ownership. Also, the bootstrap token doesn't get escrowed.

Background: in our 1:1 deployments, we have a PreStage-created, hidden local admin account. In this 1:1 PreStage, we have enrollment customization turned on, pointing to our SSO IdP. User gets a device, logs in through SSO, which populates their info into the local user creation screen. They enter their password again, and get logged into the device.

Enrollment finishes, and for some reason Jamf is showing the PreStage admin account as the only Secure Token holder and Volume Owner. And as mentioned, bootstrap Token doesn't get escrowed. I can fix manually using sudo profiles install -type bootstraptoken, but trying to figure out why this is happening? Administrator has never been signed into. And the account that is the first one logging in, isn't getting secure token / volume ownership.

Strangely, it's not quite 100% of the time. I wiped a 14.2.1 tester M1 and re-enrolled, it went as expected. I got volume ownership, secure token, and bootstrap was escrowed. Same exact PreStage, same policies, profiles, etc.

PreStage hasn't been touched lately. Yet this problem seems to have just started happening in the last week or so I believe. I put a ticket in and posted about this on the MacAdmins slack, just figured I'd post here as well.

1 ACCEPTED SOLUTION

So this ended up probably being something on our end. We were getting away from having Jamf create a Local Admin account during PreStage and making one with a script + policy instead. Due to the whole impending forced LAPS changes and the fact that we were already using macOSLAPS, etc.

There was an unforeseen race condition where it was possible for our Admin account to get created before the 1:1 user's (who the computer is getting issued to) account got created. So our script/policy-created Admin account would get UID 501 instead of the 1:1 user.

The natural process of the bootstrap token getting escrowed and stuff is expecting the 1:1 user to have a UID of 501, not our admin account. So the bootstrap token wouldn't get escrowed, and our admin account could 'steal' the securetoken and be the sole holder.

I ended up implementing a check in the script that creates our admin account + installs macOSLAPS that ensures the 1:1 users account already exists with a UID of 501 and is a securetoken holder. It will wait and retry if the 1:1 users account hasn't been created yet when it runs. I also made it so that script/policy runs last during the enrollment process.

I've had essentially zero issues with the bootstrap token getting escrowed and weird secure token behavior since then.

View solution in original post

6 REPLIES 6

obi-k
Valued Contributor III

Let us know what you find. Saw some strangeness with M3 Mac on 14.2.1 and 14.2. Couldn't downgrade, so couldn't test different OS.

Had trouble with SecureToken, even with Bootstrap Token escrowed. Could be something I'm missing, but it all worked prior to 14.2+.

dtg322s
New Contributor

Any updates on this? having similar issues.

So this ended up probably being something on our end. We were getting away from having Jamf create a Local Admin account during PreStage and making one with a script + policy instead. Due to the whole impending forced LAPS changes and the fact that we were already using macOSLAPS, etc.

There was an unforeseen race condition where it was possible for our Admin account to get created before the 1:1 user's (who the computer is getting issued to) account got created. So our script/policy-created Admin account would get UID 501 instead of the 1:1 user.

The natural process of the bootstrap token getting escrowed and stuff is expecting the 1:1 user to have a UID of 501, not our admin account. So the bootstrap token wouldn't get escrowed, and our admin account could 'steal' the securetoken and be the sole holder.

I ended up implementing a check in the script that creates our admin account + installs macOSLAPS that ensures the 1:1 users account already exists with a UID of 501 and is a securetoken holder. It will wait and retry if the 1:1 users account hasn't been created yet when it runs. I also made it so that script/policy runs last during the enrollment process.

I've had essentially zero issues with the bootstrap token getting escrowed and weird secure token behavior since then.

GabeShack
Valued Contributor III

Can you share this script, we have something similar happening

 

Gabe Shackney
Princeton Public Schools

Sure. The initial password for our LAPS account is fed through a policy parameter and is encoded. 

 

#!/bin/bash

# Ingest old Administrator password (encoded) so that if something goes wrong we have a fallback
ENCODED_PASSWORD="$4"
DECODED_PASSWORD=$(echo "${ENCODED_PASSWORD}" | base64 -D)

# First check to ensure the 1:1 user has been created *and* has SecureToken issued before creating Local Admin account
# Maximum number of retries
MAX_RETRIES=20
RETRY_COUNT=0

while [[ $RETRY_COUNT -lt $MAX_RETRIES ]]; do
    # Check for the existence of the primary user with UID 501
    PRIMARY_USER=$(dscl . -list /Users UniqueID | awk '$2 == 501 {print $1}')
    if [[ -n "$PRIMARY_USER" ]]; then
        # Check if the primary user has a SecureToken
        PRIMARY_USER_TOKEN_STATUS=$(sysadminctl -secureTokenStatus $PRIMARY_USER 2>&1)
        if [[ $PRIMARY_USER_TOKEN_STATUS =~ "ENABLED" ]]; then
            echo "Primary user with UID 501 has SecureToken, proceeding with admin account creation."

            # Create the Administrator user using jamf binary
            sudo jamf createAccount -username Administrator -realname "Administrator" -password "${DECODED_PASSWORD}" -home /Users/Administrator -admin
            
            # Manually hide the Administrator user account
            sudo dscl . create /Users/Administrator IsHidden 1
            
            # Trigger the macOSLAPS policy
            sudo jamf policy -event macoslaps
            
            exit 0
        else
            echo "Primary user with UID 501 does not have SecureToken. Retrying in 30 seconds..."
        fi
    else
        echo "Primary user with UID 501 not found. Retrying in 30 seconds..."
    fi
    
    # Wait for 30 seconds before retrying
    sleep 30
    let RETRY_COUNT=RETRY_COUNT+1
done

echo "Failed to verify SecureToken for UID 501 after $MAX_RETRIES attempts. Exiting script."
exit 1

 

Admin / LAPS account only gets created after the 1:1 users account has already been created, and they're confirmed to have a SecureToken. Once the Admin account has been created it triggers our separate policy that installs macOSLAPS and rolls the password.

We use this same script for shared Jamf Connect devices as well. Only difference there is I have a script that escrows the Bootstrap Token prior to this script running, so that our technicians don't have to manually login to each shared device after enrolling them. 

GabeShack
Valued Contributor III

FYI I ended up making a script to manually set a password for our laps accounts on apple silicone to fix some issues with volume ownership based on a smart group.

 

#!/bin/bash

# API USER
user="YOUR_API_USERNAME_HERE"
# API PASSWORD
pass="YOUR_API_USER_PASSWORD_HERE"
# URL (https://yourjamfserver.jamfcloud.com)
jurl="https://YOUR_JAMF_URL_HERE"
# Smart group or static group ID to get computer IDs from
groupID="YOUR_SMARTGROUP_ID_HERE"
# Define the admin user name
adminname="YOUR_ADMIN_USERNAME_HERE"
# New LAPS password to set
newPassword="YOUR_PASSWORD_HERE"  


# Get Bearer token for API calls
getBearerToken() {
    response=$(curl -s -u "$user:$pass" "$jurl/api/v1/auth/token" -X POST)
    token=$(echo "$response" | plutil -extract token raw -)
    tokenExpiration=$(echo "$response" | plutil -extract expires raw - | awk -F . '{print $1}')
    tokenExpirationEpoch=$(date -j -f "%Y-%m-%dT%T" "$tokenExpiration" +"%s")
    echo "Token acquired."
}

# Invalidate the token once done
invalidateToken() {
    curl -w "%{http_code}" -H "Authorization: Bearer $token" "$jurl/api/v1/auth/invalidate-token" -X POST -s -o /dev/null
    echo "Token invalidated."
}

# Check token expiration and get a new one if necessary
checkTokenExpiration() {
    nowEpochUTC=$(date -j -f "%Y-%m-%dT%T" "$(date -u +"%Y-%m-%dT%T")" +"%s")
    if [[ tokenExpirationEpoch -gt nowEpochUTC ]]; then
        echo "Token is valid."
    else
        echo "Token expired, fetching new token."
        getBearerToken
    fi
}

# Run the function to get the token
getBearerToken

# Grab all computer IDs from the smart group (ID 802)
echo "Fetching computer IDs from group ID: $groupID"
computerids=($(curl -s $jurl/JSSResource/computergroups/id/$groupID \
-H 'accept: application/xml' \
-H "Authorization: Bearer $token" | xmllint --xpath '/computer_group/computers/computer/id/text()' -))

echo "Found Computer IDs: ${computerids[@]}"

# Loop through all computer IDs to look up the managementId and reset the LAPS password
for id in "${computerids[@]}"; do
    checkTokenExpiration
    
    # Get computer details to retrieve the managementId
    computerInfo=$(curl -s "$jurl/api/v1/computers-inventory/$id?section=GENERAL" \
        -H 'accept: application/json' \
        -H "Authorization: Bearer $token")

    # Extract the managementId using jq
    managementId=$(echo "$computerInfo" | jq -r '.general.managementId')

    if [[ -z "$managementId" || "$managementId" == "null" ]]; then
        echo "No management ID found for computer $id, skipping..."
        continue
    fi

    echo "Found Management ID: $managementId for computer $id"

    # Reset the LAPS password for the macadmin user using the correct API endpoint
    curl -s -X PUT "$jurl/api/v2/local-admin-password/$managementId/set-password" \
        -H "accept: application/json" \
        -H "Authorization: Bearer $token" \
        -H "Content-Type: application/json" \
        -d "{\"lapsUserPasswordList\": [{\"username\": \"$adminname\", \"password\": \"$newPassword\"}]}" || echo "Failed to reset LAPS password for computer $id with Management ID: $managementId"

done

# Invalidate the token when done
invalidateToken

exit 0

 

Gabe Shackney
Princeton Public Schools