AD Group scoping in Jamf for macOS

Posted on Feb 3, 2020

Scoping Jamf policies for AD groups is a breeze when your users are logging in with AD credentials. But what if you’re O365/AAD only? The Jamf agent won’t recognize an AAD logon unless you’re using Jamf Connect. Sure, it’s unusual to have one without the other, but I have a solution here if you’re running lean.

In a rush? Find the script here. Create a new standard user in Jamf for the script to access the API, granting it Auditor privileges. Update the script with that username and password, then drop it into a new policy and scope it for all workstations.

Scenario

  • Jamf has connection to ADDS for auth
  • Users enrol their own devices using their AD user/pass
  • Devices are not bound to AD, nor do local usernames match AD
  • Some policies are scoped for AD Groups
  • AD Group scoped policies are not being applied to users' workstations

Reason

Simple, really. When the Jamf agent evaluates the policies, it does so using the local username. If this doesn’t match an AD username, then AD group membership isn’t found, and scoped policies don’t apply.

The AD username of the person who owns (enrolled) the device is stored in the computer’s user and location properties in Jamf Server.

Example computer object in Jamf, showing User and Location properties.

We just need a way to tell the Jamf agent to apply policies for that username.

Solution

Use the API! I was hunting around for an example and found this on Jamf Nation. The script offered by ‘ShaunRMiller83’ had almost exactly what I was looking for, that is, a bash scripted API call to pull the device user from Jamf Server.

The important part of Shaun’s script is below.

#!/bin/sh 
# Variables 
jssURL="https://jamf.domain.com:8443/" 
apiUser="apiuser" 
apiPass="apipassword" 
 

SERIAL=$(ioreg -c IOPlatformExpertDevice -d 2 | awk -F\" '/IOPlatformSerialNumber/{print $(NF-1)}') 
 

USERINFO=$(curl -k ${jssURL}JSSResource/computers/serialnumber/${SERIAL}/subset/location -H "Accept: application/xml" --user "${apiUser}:${apiPass}") 
USERNAME=$(echo $USERINFO | /usr/bin/awk -F'<username>|</username>' '{print $2}' | tr [A-Z] [a-z]) # This is our asset's owner's username.

All we need to do was call jamf policy -username with the $USERNAME variable to have the Jamf agent pull down user or group scoped policies. The following script will do this.

#!/bin/sh 
# Polls Jamf API for computer owner then requests 
# all policies for that username 
 

# Variables 
jssURL="https://<YOURDOMAIN>.jamfcloud.com/" 
apiUser="<YOURAPIUSER>" 
apiPass="<APIUSERPASS>" 
 

SERIAL=$(ioreg -c IOPlatformExpertDevice -d 2 | awk -F\" '/IOPlatformSerialNumber/{print $(NF-1)}') 
USERINFO=$(curl -s -k ${jssURL}JSSResource/computers/serialnumber/${SERIAL}/subset/location -H "Accept: application/xml" --user "${apiUser}:${apiPass}") 
USERNAME=$(echo $USERINFO | /usr/bin/awk -F'<username>|</username>' '{print $2}' | tr [A-Z] [a-z]) 
 

printf "%s %s\n" "Processing policy for user:" $USERNAME 
/usr/local/jamf/bin/jamf policy -username $USERNAME 

A prerequisite for the script is an API Username and Password combo for the script to use to authenticate with the Jamf API. Simply create a new standard user in Jamf, granting it Auditor privileges. Use the username and password in the script.

Uh-oh! If we just upload this script to Jamf and have it run in a policy, as jamf will complain that it’s already busy processing policy. So we’ll need to drop this script somewhere on the assets and configure a scheduled task to trigger it every so often.

Hidden commands in the jamf binary help us here, specifically the jamf scheduledTask verb. This will create a LaunchDaemon with a specified command, user, and schedule.

jamf scheduledTask -command "/path/to/our/script/getuserpolicy.sh" -name GetADUserPolicies -user root -runAtLoad -minute '*/30/'

We can wrap this all up in a single script that will:

  • Drop the getuserpolicy.sh script on the system in /usr/local/jamf/bin
  • Make the script executable with a chmod +x
  • Run the jamf scheduledTask command to schedule script execution for every thirty minutes

Find the final version of this script below.

Did this script help you? I’d love to hear about it! Comment below, or reach out to me on twitter.