Skip to main content

ssh.nu

Syncing CrowdStrike ZTA score to Jamf

Table of Contents

# Hey Victor,

Can you write a dirty, probably worst-practice guide on how to sync the ZTA score from CrowdStrike to objects in Jamf, so that the score can be reused for example during Microsoft Conditional Access compliance checks?

Hold my compliant-without-any-real-checks device, I'm going in!
⚠️ Warning: This post (well, the whole blog) is more like a notepad for myself, and I cannot recommend applying my fix/es for production use before consulting a professional. As always with my stuff, it works, but is probably not the best way to solve the problem.  

# Fetching the ZTA score from the device

All that we want are the ZTA scores (There are actually 3 scores, os, sensor_config and overall). We need to get them from every device, but how?
 

I know we can either get the scores from the CrowdStrike API, or, from the device itself. It’s a terrible idea to ask the device to give us information that we’ll use for evaluation of device posture for other systems. Since it’s locally stored and we ask the device itself - the info could quite easily be spoofed, but it’s an easy way to achieve what we want.

In the Mac Admins Slack, I found some ideas and even script examples on how to fetch it from the device, spoiler alert: reading the contents of /Library/Application Support/CrowdStrike/ZeroTrustAssessment/data.zta.

The data.zta file is a base64 encoded file which contains the device’s ZTA score(s).  

However, sadly, luckily, so we don’t have to trust what the end user device tells us, the file on my machine and a few other test devices, are empty, so there’s nothing to be read:

~ % file "/Library/Application Support/CrowdStrike/ZeroTrustAssessment/data.zta"
/Library/Application Support/CrowdStrike/ZeroTrustAssessment/data.zta: empty

~ % ls -hlia "/Library/Application Support/CrowdStrike/ZeroTrustAssessment/data.zta"
12717011 -rw-r--r--+ 1 root  wheel     0B 16 Jul 01:08 /Library/Application Support/CrowdStrike/ZeroTrustAssessment/data.zta

Okay, so in a true zero trust manner, we need to ask the vendor directly for the data!

# Scoping relevant devices only!

We do not need to update ZTA score for inactive devices, it’ll just use unnecessary compute power and possibly rate limit us from the APIs, so we’ll create a Smart Computer Group containing only devices that checked into Jamf within the last 24 hours.
This is our $SCOPED_HOSTS_GROUP.

In theory, we could also ask CrowdStrike the same (how about last_seen from a device?), but let’s do it via Jamf this time. I haven’t used their product much at all, so it’s a great way of learning.

## 1+1=1

Two systems, one device.
Writing the wrong score to a device might have severe consequences (marking an infected machine as compliant and giving it access to things, eventhough it’s an unsafe device).

We need a unique, common denominator for our machines on both the CrowdStrike and the Jamf side. The first thing that comes to mind is hostname, but since some people think changing the machine’s name is a good idea, let’s not filter on that.

How about Serial Number, since both CrowdStrike and Jamf adds that to their inventories by default? And as a bonus, all devices are made by the same vendor (Apple) so there should never be any risk for a serial number collision either!

## Jamf inventory

Since we know which devices we want the serials from, we can query the members of our Jamf smart group via /JSSResource/computergroups/id/$SCOPED_HOSTS_GROUP.
This’ll give us a JSON containing all the devices in the group, and guess what? It already contains what we want - the device’s serial_number!
Let’s generate a list of the serial numbers to update:

JAMF_GROUP=$(curl -s "$JAMF_APIBASE/JSSResource/computergroups/id/$SCOPED_HOSTS_GROUP" -H 'accept: application/json' -H "Authorization: Bearer $JAMF_TOKEN")
LIST_OF_DEVICES=$(echo $JAMF_GROUP | jq -r '.computer_group.computers[].serial_number')

## CrowdStrike inventory

As for the CrowdStrike side, we not only need the serial number, but we need the AID, which is explained as:

an unalterable unique identifier for the device within the CrowdStrike environment and remains constant even when other identifiable characteristics such as IP address, MAC Address and Hostnames are changed.

We can get the AID for each device from its’ serial, querying /devices/queries/devices/v1, as such:

LIST_DEVICE_URL="$CROWDSTRIKE_APIBASE/devices/queries/devices/v1"
CS_DEVICE_RESPONSE=$(curl -s -H "Authorization: Bearer $CROWDSTRIKE_TOKEN" "$LIST_DEVICE_URL?filter=serial_number:'$SERIAL_NUM'")
CS_DEVICE_ID=$(echo "$CS_DEVICE_RESPONSE" | jq -r '.resources[0]')

# Getting the ZTA score from the CrowdStrike API

Querying the /zero-trust-assessment/entities/assessments/v1 endpoint, filtering on the machine’s AID (?ids=$CS_DEVICE_ID), we receive a nice JSON back, containing all 3 scores, sensor, OS and overall:

[...]
   "assessment": {
    "sensor_config": 100,
    "os": 96,
    "overall": 99,
    "version": "3.8.1"
   },
[...]

As a bonus, it also contains which checks pass and which fail, which, if we feel generous, we can provide to noncompliant-users.

We can save the overall score from the device to a variable:

ZTA_OVERALL_SCORE=$(echo $CS_ZTA_RESPONSE | jq -r '.resources[].assessment.overall')

And we’ll do the same for the other two scores, os and sensor_config.

# Okay, we’ve got the score/s we wanted, let’s give it to Jamf!

The easiest(?) way of saving this to a computer object this would be using a Computer Extension Attribute (text field) on every device, containing the latest ZTA score.
So let’s go ahead and add that extension attribute, and note its’ ID ($ZTA_EXTENSION_ATTRIBUTE_ID) down.

In the API Docs for /computers/id/$id, we see that we can just feed the API with an XML containing the desired value, as such:

<computer>
  <extension_attributes>
    <extension_attribute>
      <id>$ZTA_EXTENSION_ATTRIBUTE_ID</id>
      <value>$ZTA_SCORE</value>
    </extension_attribute>
  </extension_attributes>
</computer>

And the extension attribute we created will be populated with the ZTA score!
We can, of course, update multiple extension attributes at once (we’ll write all 3).

ZTAXML="<computer><extension_attributes><extension_attribute><id>$ZTA_OS_ATTRIBUTE_ID</id><value>$ZTA_OS_SCORE</value></extension_attribute><extension_attribute><id>$ZTA_SENSOR_ATTRIBUTE_ID</id><value>$ZTA_SENSOR_SCORE</value></extension_attribute><extension_attribute><id>$ZTA_OVERALL_ATTRIBUTE_ID</id><value>$ZTA_OVERALL_SCORE</value></extension_attribute></extension_attributes></computer>"

curl -s --output /dev/null -X PUT "$JAMF_APIBASE/JSSResource/computers/id/$JAMF_DEVICE_ID" -H "Authorization: Bearer $JAMF_TOKEN" -H "Content-Type: text/xml" -d "$ZTAXML"

# Testing (in prod!)

The full sync-zta.sh script (it’s meant to be run as a cron job, as often as you want to refresh the ZTA scores) can be found on GitHub.

Running the script loops through all devices in our smart group, fetches its’ AID + ZTA score, and updates the Jamf object.
running

Taking a look at the Jamf object now, we can see that the extension attribute is properly populated! done

We can now re-use these value wherever we want, for example in a Compliance Group.

# TODO?

The Jamf API is slow. Writing the ZTA score of 373 devices takes a long time. Almost 10 minutes.

real	9m46.214s
user	1m29.759s
sys	0m8.579s

Luckily enough, I only have a handful of hundred devices, but for larger environments that want for some reason might want to implement this method of writing the ZTA scores, it’d take A LONG TIME.
I suppose one should update machines in parallel, however the JAMF API Best Practices tell us that we cannot have more than 5 concurrent connections, so do that on your own risk.