### Setup Jamf Protect Event Analysis Environment
Source: https://github.com/jamf/jamfprotect/blob/main/helper_tools/jamf_protect_event_analysis/README.md
Commands to clone the repository, create a Python virtual environment, and install necessary dependencies for the analysis tool.
```bash
git clone https://github.com/jamf/jamfprotect.git
cd jamfprotect/helper_tools/jamf_protect_event_analysis
python3 -m venv ~/Documents/py3-jp
source ~/Documents/py3-jp/bin/activate
pip3 install -r requirements.txt
```
--------------------------------
### Build Google Cloud Aftermath PKG
Source: https://github.com/jamf/jamfprotect/blob/main/soar_playbooks/aftermath_collection/google_cloud_storage/README.md
This Makefile snippet demonstrates how to build a package for Google Cloud integration with Aftermath. It requires the Google Cloud SDK to be installed and configured.
```makefile
sudo make pkg
```
```makefile
sudo make clean
```
--------------------------------
### Making GraphQL API Calls
Source: https://context7.com/jamf/jamfprotect/llms.txt
This section provides a utility function to make GraphQL API calls to the Jamf Protect API. It demonstrates how to send queries and handle paginated responses, using the 'listComputers' query as an example.
```APIDOC
## Making GraphQL API Calls
### Description
Sends a GraphQL query to the Jamf Protect API and handles the response. Includes an example for listing computers with pagination.
### Method
POST
### Endpoint
`https://{your-tenant}.protect.jamfcloud.com/graphql`
### Parameters
#### Request Body
- **query** (string) - Required - The GraphQL query string.
- **variables** (object) - Optional - Variables to be used in the GraphQL query.
### Headers
- **Authorization** (string) - Required - The Bearer access token obtained from the authentication endpoint.
### Request Example
```python
import requests
def make_api_call(protect_instance, access_token, query, variables=None):
"""Sends a GraphQL query to the Jamf Protect API and returns the response."""
if variables is None:
variables = {}
api_url = f"https://{protect_instance}.protect.jamfcloud.com/graphql"
payload = {"query": query, "variables": variables}
headers = {"Authorization": access_token}
resp = requests.post(api_url, json=payload, headers=headers)
resp.raise_for_status()
return resp.json()
# Example: List computers with pagination
LIST_COMPUTERS_QUERY = """
query listComputers($page_size: Int, $next: String) {
listComputers(input: { pageSize: $page_size, next: $next }) {
items {
hostName
serial
uuid
insightsUpdated
insightsStatsFail
insightsStatsPass
}
pageInfo {
next
}
}
}
"""
# Retrieve all computers with pagination
# computers = []
# next_token = None
# while True:
# vars = {"page_size": 100, "next": next_token}
# resp = make_api_call(PROTECT_INSTANCE, access_token, LIST_COMPUTERS_QUERY, vars)
# next_token = resp["data"]["listComputers"]["pageInfo"]["next"]
# computers.extend(resp["data"]["listComputers"]["items"])
# if next_token is None:
# break
# print(f"Found {len(computers)} computers")
```
### Response
#### Success Response (200)
- **data** (object) - The response data from the GraphQL query.
#### Response Example
```json
{
"data": {
"listComputers": {
"items": [
{
"hostName": "MacBook-Pro.local",
"serial": "C02XXXXXX123",
"uuid": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"insightsUpdated": "2023-10-27T10:00:00Z",
"insightsStatsFail": 0,
"insightsStatsPass": 15
}
],
"pageInfo": {
"next": "some_next_token_or_null"
}
}
}
}
```
```
--------------------------------
### Get Jamf Protect Binary Version using Jamf Pro Extension Attribute
Source: https://context7.com/jamf/jamfprotect/llms.txt
This shell script serves as a Jamf Pro Extension Attribute to retrieve the version of the Jamf Protect binary. It checks for the binary's existence at '/usr/local/bin/protectctl', extracts the version from its plist information, and returns the version string or an error message if the binary is not found.
```bash
#!/bin/sh
# Extension Attribute: Jamf Protect Binary Version
# Data Type: String
# Input Type: Script
jamfProtectBinaryLocation="/usr/local/bin/protectctl"
if [ -f "$jamfProtectBinaryLocation" ]; then
plist=$($jamfProtectBinaryLocation info --plist)
jamfProtectVersion=$(/usr/libexec/PlistBuddy -c "Print Version" /dev/stdin <<<"$plist")
else
jamfProtectVersion="Protect binary not found"
fi
echo "$jamfProtectVersion"
```
--------------------------------
### Test Unified Log Filter Predicates Locally with macOS log command
Source: https://context7.com/jamf/jamfprotect/llms.txt
This bash script demonstrates how to test unified log filter predicates locally on macOS using the 'log stream' command. It shows an example of monitoring failed sudo password attempts by streaming logs with a specific predicate and then triggering the event in a separate terminal.
```bash
# Test a unified log filter predicate locally
# Example: Monitor failed sudo password attempts
# Start streaming logs with the predicate
log stream --predicate 'process == "sudo" AND eventMessage CONTAINS[c] "TTY" AND eventMessage CONTAINS[c] "3 incorrect password attempts"'
# In another terminal, trigger the event:
sudo whoami
# Enter incorrect password 3 times
# Expected output in first terminal:
# 2024-01-15 10:30:45.123456+0000 0xXXXXc Default 0x0 XXXXX 0 sudo: username : 3 incorrect password attempts ; TTY=ttys003 ; PWD=/Users/username ; USER=root ; COMMAND=/usr/bin/whoami
```
--------------------------------
### Configure Google Cloud CLI for gsutil
Source: https://github.com/jamf/jamfprotect/blob/main/soar_playbooks/aftermath_collection/google_cloud_storage/README.md
This command configures the Google Cloud SDK's gsutil tool for authentication. It prompts the user for account information and creates necessary configuration files.
```bash
/usr/local/google-cloud-sdk/bin/gsutil config -a
```
--------------------------------
### Export Alerts by Severity in Python
Source: https://context7.com/jamf/jamfprotect/llms.txt
Demonstrates how to paginate through all alerts within a specific severity range and save the results to a JSON file. It handles API pagination using a next_token loop.
```python
results = []
next_token = None
while True:
vars = {
"min_severity": MIN_SEVERITY,
"max_severity": MAX_SEVERITY,
"page_size": 100,
"next": next_token,
}
resp = make_api_call(PROTECT_INSTANCE, access_token, LIST_ALERTS_QUERY, vars)
next_token = resp["data"]["listAlerts"]["pageInfo"]["next"]
results.extend(resp["data"]["listAlerts"]["items"])
if next_token is None:
break
print(f"Found {len(results)} alerts matching filter.")
with open(JSON_OUTPUT_FILE, "w") as output:
json.dump(results, output, sort_keys=True, indent=4)
```
--------------------------------
### AWS CLI Configuration and Package Build Commands
Source: https://github.com/jamf/jamfprotect/blob/main/soar_playbooks/aftermath_collection/aws_s3/README.md
Terminal commands to configure the AWS CLI profile for Aftermath and build the deployment package using the provided Makefile.
```bash
aws configure --profile aftermath
aws configure list --profile aftermath
sudo make pkg
sudo make clean
```
--------------------------------
### Run Jamf Protect Event Analysis
Source: https://github.com/jamf/jamfprotect/blob/main/helper_tools/jamf_protect_event_analysis/README.md
Executes the event analysis tool to monitor process activity for 60 seconds and generate a summary report. This helps identify top-counted processes for potential exclusion rules.
```bash
source ~/Documents/py3-jp/bin/activate
./jp_event_analysis.py -m Process -s
```
--------------------------------
### Implement Endpoint Network Isolation SOAR Playbook
Source: https://context7.com/jamf/jamfprotect/llms.txt
This bash script implements a SOAR playbook for endpoint network isolation using macOS Packet Filter (pfctl). It configures pf rules to block all traffic except for essential Apple and Jamf Pro communication, and sets up a LaunchDaemon to ensure the isolation persists across reboots. The script dynamically retrieves the Jamf Pro instance URL.
```bash
#!/bin/bash
# Endpoint Network Isolation - Enforce
# Isolates endpoint using pfctl, maintaining Jamf Pro and Apple APNs connectivity
# Configuration
apnsIPRange="17.0.0.0/8" # Apple Push Notification Service
fileName="com.acmesoft.isolate"
# Get Jamf Pro URL from preferences
JamfProInstance=$(/usr/bin/defaults read /Library/Preferences/com.jamfsoftware.jamf.plist jss_url | sed -e 's/^ *.*:\/\///g' -e 's/.$//')
# Create PF anchor configuration
/usr/bin/tee /etc/pf.anchors/"$fileName".pf.conf <
Label
${fileName}.pf.plist
Program
/sbin/pfctl
ProgramArguments
/sbin/pfctl
-e
-f
/etc/pf.anchors/${fileName}.pf.conf
RunAtLoad
EOF
# Enable isolation
/sbin/pfctl -d
/bin/launchctl load -w /Library/LaunchDaemons/"$fileName".pf.plist
echo "Endpoint network isolation enabled"
```
--------------------------------
### Capture Unified Logs for Analysis
Source: https://github.com/jamf/jamfprotect/blob/main/helper_tools/jamf_protect_event_analysis/README.md
Captures system logs in JSON format for a specific Jamf Protect monitor category, which can then be passed to the analysis tool using the -i argument.
```bash
log stream --info --debug --style json --timeout --predicate 'subsystem BEGINSWITH "com.jamf.protect" AND category == ""' > log_file.json
```
--------------------------------
### Authenticate Jamf Protect GraphQL API with Python
Source: https://context7.com/jamf/jamfprotect/llms.txt
This Python script demonstrates how to obtain an access token for authenticating requests to the Jamf Protect API using client credentials. It requires the Jamf Protect instance URL, client ID, and API password. The function returns a reusable access token.
```python
import requests
PROTECT_INSTANCE = "your-tenant" # From https://your-tenant.protect.jamfcloud.com
CLIENT_ID = "your-client-id"
PASSWORD = "your-api-password"
def get_access_token(protect_instance, client_id, password):
"""Gets a reusable access token to authenticate requests to the Jamf Protect API"""
token_url = f"https://{protect_instance}.protect.jamfcloud.com/token"
payload = {
"client_id": client_id,
"password": password,
}
resp = requests.post(token_url, json=payload)
resp.raise_for_status()
resp_data = resp.json()
print(f"Access token granted, valid for {int(resp_data['expires_in'] // 60)} minutes.")
return resp_data["access_token"]
# Get token
access_token = get_access_token(PROTECT_INSTANCE, CLIENT_ID, PASSWORD)
# Output: Access token granted, valid for 60 minutes.
```
--------------------------------
### Run Aftermath Scan and Collect Data
Source: https://github.com/jamf/jamfprotect/blob/main/soar_playbooks/aftermath_collection/google_cloud_storage/README.md
This policy triggers an Aftermath scan and then initiates a data collection event. It's designed to be run on endpoints managed by Jamf Pro.
```bash
/usr/local/bin/aftermath --pretty; /usr/local/bin/jamf policy -event am_collect
```
--------------------------------
### Detect Keychain Dump Attempts via Process Events
Source: https://context7.com/jamf/jamfprotect/llms.txt
This analytic monitors process creation events to identify attempts to dump the macOS keychain using the security binary. It filters for specific signing information and command-line arguments.
```yaml
name: KeychainDumped
uuid: 6fe79b15-aa2f-4bd5-a6c9-9192878f17b4
inputType: GPProcessEvent
filter: >
$event.type == 1 AND
$event.process.signingInfo.appid == "com.apple.security" AND
$event.process.args.@count > 0 AND
(ANY $event.process.args == "dump-keychain")
actions:
- name: Log
```
--------------------------------
### Query Audit Logs by Date Range in Python
Source: https://context7.com/jamf/jamfprotect/llms.txt
Retrieves administrative activity logs within a specified date range using the listAuditLogsByDate query. It implements pagination to ensure all logs are captured.
```python
import datetime as dt
now = dt.datetime.now(dt.timezone.utc)
start_date = (now - dt.timedelta(days=7)).strftime("%Y-%m-%dT%H:%M") + ":00.000Z"
end_date = now.strftime("%Y-%m-%dT%H:%M") + ":00.000Z"
variables = {
"input": {
"condition": {
"dateRange": {
"startDate": start_date,
"endDate": end_date,
}
}
}
}
items = []
next_token = ""
while next_token is not None:
variables["input"]["next"] = next_token
resp = make_api_call(PROTECT_INSTANCE, access_token, LIST_AUDIT_LOGS_BY_DATE, variables)
items.extend(resp["data"]["listAuditLogsByDate"]["items"])
next_token = resp["data"]["listAuditLogsByDate"]["pageInfo"]["next"]
```
--------------------------------
### Export Non-Compliant Insights in Bash
Source: https://context7.com/jamf/jamfprotect/llms.txt
Uses curl and jq to fetch non-compliant computer insights from Jamf Protect and exports the failing entries into a CSV file. It handles authentication, GraphQL querying, and JSON data transformation.
```bash
#!/bin/bash
# ... (authentication and query setup)
while true; do
request_json=$(jq -n \
--arg q "$LIST_COMPUTERS_QUERY" \
--argjson next_token $next_token \
'{"query": $q, "variables": {"next": $next_token}}'
)
list_computers_resp=$(curl --silent --request POST --header "Authorization: ${access_token}" --url "https://${PROTECT_INSTANCE}.protect.jamfcloud.com/graphql" --data "$request_json")
jq -r '.data.listComputers.items[] | {hostName: .hostName, scorecard_entry: .scorecard[]} | select(.scorecard_entry.pass == false) | select(.scorecard_entry.enabled == true) | [.hostName, .scorecard_entry.label, .scorecard_entry.pass] | @csv' <<< "$list_computers_resp" >> "$OUTPUT_CSV_FILE"
next_token=$(jq '.data.listComputers.pageInfo.next' <<< "$list_computers_resp")
[[ $next_token == "null" ]] && break
done
```
--------------------------------
### Make GraphQL API Calls to Jamf Protect with Python
Source: https://context7.com/jamf/jamfprotect/llms.txt
This Python function sends GraphQL queries to the Jamf Protect API using a provided access token. It supports variables for dynamic queries and handles pagination for large datasets. The function returns the JSON response from the API.
```python
import requests
def make_api_call(protect_instance, access_token, query, variables=None):
"""Sends a GraphQL query to the Jamf Protect API and returns the response."""
if variables is None:
variables = {}
api_url = f"https://{protect_instance}.protect.jamfcloud.com/graphql"
payload = {"query": query, "variables": variables}
headers = {"Authorization": access_token}
resp = requests.post(api_url, json=payload, headers=headers)
resp.raise_for_status()
return resp.json()
# Example: List computers with pagination
LIST_COMPUTERS_QUERY = """
query listComputers($page_size: Int, $next: String) {
listComputers(input: { pageSize: $page_size, next: $next }) {
items {
hostName
serial
uuid
insightsUpdated
insightsStatsFail
insightsStatsPass
}
pageInfo {
next
}
}
}
"""
# Retrieve all computers with pagination
computers = []
next_token = None
while True:
vars = {"page_size": 100, "next": next_token}
resp = make_api_call(PROTECT_INSTANCE, access_token, LIST_COMPUTERS_QUERY, vars)
next_token = resp["data"]["listComputers"]["pageInfo"]["next"]
computers.extend(resp["data"]["listComputers"]["items"])
if next_token is None:
break
print(f"Found {len(computers)} computers")
```
--------------------------------
### Jamf Protect GraphQL API Authentication
Source: https://context7.com/jamf/jamfprotect/llms.txt
This section details how to obtain an access token for authenticating with the Jamf Protect GraphQL API. It requires your Jamf Protect instance URL, client ID, and API password.
```APIDOC
## Jamf Protect GraphQL API Authentication
### Description
Authenticates with the Jamf Protect API to obtain a reusable access token.
### Method
POST
### Endpoint
`https://{your-tenant}.protect.jamfcloud.com/token`
### Parameters
#### Request Body
- **client_id** (string) - Required - Your Jamf Protect API client ID.
- **password** (string) - Required - Your Jamf Protect API password.
### Request Example
```python
import requests
PROTECT_INSTANCE = "your-tenant" # From https://your-tenant.protect.jamfcloud.com
CLIENT_ID = "your-client-id"
PASSWORD = "your-api-password"
def get_access_token(protect_instance, client_id, password):
"""Gets a reusable access token to authenticate requests to the Jamf Protect API"""
token_url = f"https://{protect_instance}.protect.jamfcloud.com/token"
payload = {
"client_id": client_id,
"password": password,
}
resp = requests.post(token_url, json=payload)
resp.raise_for_status()
resp_data = resp.json()
print(f"Access token granted, valid for {int(resp_data['expires_in'] // 60)} minutes.")
return resp_data["access_token"]
# Get token
access_token = get_access_token(PROTECT_INSTANCE, CLIENT_ID, PASSWORD)
```
### Response
#### Success Response (200)
- **access_token** (string) - The obtained access token.
- **expires_in** (integer) - The token's validity period in seconds.
#### Response Example
```json
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_in": 3600,
"token_type": "Bearer"
}
```
```
--------------------------------
### JSON Alert Schema - GPProcessEvent
Source: https://context7.com/jamf/jamfprotect/llms.txt
This schema defines the structure of process events captured by Jamf Protect, including detailed information about the process tree, signing status, and MITRE ATT&CK categorization for threat intelligence.
```APIDOC
## JSON Alert Schema - GPProcessEvent
Process events include full process tree, signing information, and MITRE ATT&CK categorization for threat intelligence correlation.
### Request Body
```json
{
"host": {
"os": "Version 14.0 (Build 23A344)",
"ips": ["192.168.11.226"],
"serial": "Z2C23RW4DY",
"hostname": "VMAC-2C23RW4DY",
"protectVersion": "5.1.0.4",
"provisioningUDID": "0000FE00-8406CE28ECFC4DAB"
},
"match": {
"tags": ["MITREattack", "DefenseEvasion", "T1548.004", "PrivilegeEscalation"],
"uuid": "7232d4a4-2289-49ba-a218-215ef3d62ec4",
"event": {
"pid": 3136,
"type": 1,
"uuid": "e19385fc-6077-4d00-ad56-b89eec15e730",
"timestamp": 1698841238.851668
},
"facts": [
{
"name": "User Elevated Action",
"human": "Application used deprecated elevation API",
"severity": 0,
"actions": [{"name": "Report"}]
}
],
"severity": 0
},
"related": {
"users": [
{"uid": 0, "name": "root"},
{"uid": 501, "name": "local-admin"}
],
"processes": [
{
"pid": 3136,
"name": "security_authtrampoline",
"path": "/usr/libexec/security_authtrampoline",
"args": ["/usr/libexec/security_authtrampoline", "/Library/Application Support/JAMF/Remote Assist/Wipe", "auth 16"],
"signingInfo": {
"appid": "com.apple.security_authtrampoline",
"status": 0,
"signerType": 0,
"authorities": ["Software Signing", "Apple Code Signing Certification Authority", "Apple Root CA"]
}
}
]
},
"eventType": "GPProcessEvent"
}
```
```
--------------------------------
### Detect Hosts File Modifications via File System Events
Source: https://context7.com/jamf/jamfprotect/llms.txt
This analytic monitors file system events to detect unauthorized modifications to the /etc/hosts file. It uses case-insensitive path matching to ensure detection.
```yaml
name: HostFileModification
uuid: 7eea8332-eeb4-416f-92d8-cccef960f678
inputType: GPFSEvent
filter: >
$event.isModified == 1 AND
$event.path ==[cd] "/private/etc/hosts"
actions:
- name: Log
```
--------------------------------
### Export Jamf Protect Alert Data with Severity Filtering in Python
Source: https://context7.com/jamf/jamfprotect/llms.txt
This Python script retrieves security alerts from Jamf Protect, filtering by severity levels (e.g., Low to High). It outputs the results to a JSON file, including the alert JSON payload, severity, hostname, and creation timestamp. It utilizes the `listAlerts` GraphQL query.
```python
import json
from datetime import datetime
MIN_SEVERITY = "Low"
MAX_SEVERITY = "High"
JSON_OUTPUT_FILE = f"Jamf_Protect_Alerts_{datetime.utcnow().strftime('%Y-%m-%d')}.json"
LIST_ALERTS_QUERY = """
query listAlerts(
$min_severity: SEVERITY
$max_severity: SEVERITY
$page_size: Int
$next: String
) {
listAlerts(
input: {
filter: {
severity: { greaterThanOrEqual: $min_severity }
and: { severity: { lessThanOrEqual: $max_severity } }
}
pageSize: $page_size
next: $next
}
) {
items {
json
severity
computer { hostName }
created
}
pageInfo { next }
}
}
"""
# This script would typically call make_api_call and then process/save the results.
```
--------------------------------
### Monitor Lock Screen Unlock Failures
Source: https://context7.com/jamf/jamfprotect/llms.txt
A unified log filter that tracks failed unlock attempts by monitoring the loginwindow process for incorrect password indicators.
```yaml
name: "Lock Screen Unlock Failure"
predicate: >
processImagePath BEGINSWITH "/System/Library/CoreServices" AND
process == "loginwindow" AND
eventMessage CONTAINS[c] "INCORRECT"
```
--------------------------------
### Configure Application Firewall Logging Unified Log Filter
Source: https://context7.com/jamf/jamfprotect/llms.txt
This YAML configuration defines a unified log filter for streaming Application Firewall logs from macOS. It targets logs from the 'com.apple.alf' subsystem and is enabled by default. Note that private data is redacted unless a specific configuration profile is applied.
```yaml
# unified_log_filters/application_firewall_logging.yaml
name: "Application Firewall Logging"
description: "Reports on logging from the Application Firewall in macOS via the com.apple.alf subsystem."
predicate: 'subsystem == "com.apple.alf"'
tags:
- visibility
enabled: true
# Note: Private data (IP addresses, ports) is redacted by default.
# Enable private data logging with a configuration profile from:
# https://github.com/usnistgov/macos_security/blob/monterey/includes/com.apple.alf.private_data.mobileconfig
```
--------------------------------
### Jamf Protect GPProcessEvent JSON Schema
Source: https://context7.com/jamf/jamfprotect/llms.txt
This JSON schema defines the structure for GPProcessEvent alerts from Jamf Protect. It includes host details, match information with MITRE ATT&CK tags, event specifics, related user and process data, and signing information. This schema is essential for integrating Jamf Protect alerts with SIEM platforms and threat intelligence systems.
```json
{
"host": {
"os": "Version 14.0 (Build 23A344)",
"ips": ["192.168.11.226"],
"serial": "Z2C23RW4DY",
"hostname": "VMAC-2C23RW4DY",
"protectVersion": "5.1.0.4",
"provisioningUDID": "0000FE00-8406CE28ECFC4DAB"
},
"match": {
"tags": ["MITREattack", "DefenseEvasion", "T1548.004", "PrivilegeEscalation"],
"uuid": "7232d4a4-2289-49ba-a218-215ef3d62ec4",
"event": {
"pid": 3136,
"type": 1,
"uuid": "e19385fc-6077-4d00-ad56-b89eec15e730",
"timestamp": 1698841238.851668
},
"facts": [{
"name": "User Elevated Action",
"human": "Application used deprecated elevation API",
"severity": 0,
"actions": [{"name": "Report"}]
}],
"severity": 0
},
"related": {
"users": [
{"uid": 0, "name": "root"},
{"uid": 501, "name": "local-admin"}
],
"processes": [{
"pid": 3136,
"name": "security_authtrampoline",
"path": "/usr/libexec/security_authtrampoline",
"args": ["/usr/libexec/security_authtrampoline", "/Library/Application Support/JAMF/Remote Assist/Wipe", "auth 16"],
"signingInfo": {
"appid": "com.apple.security_authtrampoline",
"status": 0,
"signerType": 0,
"authorities": ["Software Signing", "Apple Code Signing Certification Authority", "Apple Root CA"]
}
}]
},
"eventType": "GPProcessEvent"
}
```
--------------------------------
### Detect Hidden Account Creation via DSCL
Source: https://context7.com/jamf/jamfprotect/llms.txt
This analytic monitors process events for the dscl utility to detect the creation of hidden user accounts. It checks for specific flags indicating account concealment.
```yaml
name: HiddenAccountCreatedDscl
uuid: 7ee555e8-d911-4918-a9f1-c919675d02c5
inputType: GPProcessEvent
filter: >
$event.type == 1 AND
($event.process.path.lastPathComponent == "dscl" AND
((ANY $event.process.args == "IsHidden") AND
(ANY $event.process.args == "-create") AND
(ANY $event.process.args IN {"true", "1", "yes"})))
```
--------------------------------
### Check for Jamf Protect Quarantined Files using Jamf Pro Extension Attribute
Source: https://context7.com/jamf/jamfprotect/llms.txt
This bash script functions as a Jamf Pro Extension Attribute to report whether any files are present in Jamf Protect's quarantine directory. It checks the '/Library/Application Support/JamfProtect/Quarantine' directory and outputs 'Yes' if files are found, and 'No' otherwise.
```bash
#!/bin/bash
# Extension Attribute: Jamf Protect Quarantined Files
# Data Type: String
# Input Type: Script
QUARANTINE_FILES=$(/bin/ls /Library/Application\ Support/JamfProtect/Quarantine)
if [[ -z "$QUARANTINE_FILES" ]]; then
echo "No"
else
echo "Yes"
fi
```
--------------------------------
### Export Alert Data with Severity Filtering
Source: https://context7.com/jamf/jamfprotect/llms.txt
This section demonstrates how to use the 'listAlerts' GraphQL query to retrieve security alerts. It includes filtering by severity level (e.g., Low to High) and paginating through results.
```APIDOC
## Export Alert Data with Severity Filtering
### Description
Retrieves security alerts from Jamf Protect, with the ability to filter by minimum and maximum severity levels. Results include alert details, severity, associated computer hostname, and creation timestamp.
### Method
POST
### Endpoint
`https://{your-tenant}.protect.jamfcloud.com/graphql`
### Parameters
#### Query Parameters (within the GraphQL query)
- **min_severity** (SEVERITY) - Optional - The minimum severity level to filter alerts (e.g., "Informational", "Low", "Medium", "High").
- **max_severity** (SEVERITY) - Optional - The maximum severity level to filter alerts.
- **page_size** (Int) - Optional - The number of items to return per page.
- **next** (String) - Optional - A token for fetching the next page of results.
#### Request Body (GraphQL Payload)
- **query** (string) - Required - The GraphQL query string for `listAlerts`.
- **variables** (object) - Optional - Variables for the query, including severity filters and pagination tokens.
### Headers
- **Authorization** (string) - Required - The Bearer access token.
### Request Example
```python
import json
from datetime import datetime
# Assume PROTECT_INSTANCE and access_token are defined from the authentication step
MIN_SEVERITY = "Low"
MAX_SEVERITY = "High"
JSON_OUTPUT_FILE = f"Jamf_Protect_Alerts_{datetime.utcnow().strftime('%Y-%m-%d')}.json"
LIST_ALERTS_QUERY = """
query listAlerts(
$min_severity: SEVERITY
$max_severity: SEVERITY
$page_size: Int
$next: String
) {
listAlerts(
input: {
filter: {
severity: { greaterThanOrEqual: $min_severity }
and: { severity: { lessThanOrEqual: $max_severity } }
}
pageSize: $page_size
next: $next
}
) {
items {
json
severity
computer { hostName }
created
}
pageInfo { next }
}
}
"""
# Example of making the call and handling pagination (similar to listComputers)
# alerts = []
# next_token = None
# while True:
# vars = {"min_severity": MIN_SEVERITY, "max_severity": MAX_SEVERITY, "page_size": 100, "next": next_token}
# resp = make_api_call(PROTECT_INSTANCE, access_token, LIST_ALERTS_QUERY, vars)
# next_token = resp["data"]["listAlerts"]["pageInfo"]["next"]
# alerts.extend(resp["data"]["listAlerts"]["items"])
# if next_token is None:
# break
# print(f"Found {len(alerts)} alerts")
# with open(JSON_OUTPUT_FILE, 'w') as f:
# json.dump(alerts, f, indent=2)
```
### Response
#### Success Response (200)
- **data.listAlerts.items** (array) - An array of alert objects.
- **json** (object) - The full JSON payload of the alert.
- **severity** (string) - The severity level of the alert (e.g., "Low", "Medium", "High").
- **computer.hostName** (string) - The hostname of the computer the alert is associated with.
- **created** (string) - The timestamp when the alert was created (ISO 8601 format).
- **data.listAlerts.pageInfo.next** (string) - A token for the next page of results, or null if it's the last page.
#### Response Example
```json
{
"data": {
"listAlerts": {
"items": [
{
"json": { ... alert details ... },
"severity": "Medium",
"computer": {
"hostName": "Developer-Mac.local"
},
"created": "2023-10-27T11:30:00Z"
}
],
"pageInfo": {
"next": "another_next_token_or_null"
}
}
}
}
```
```
--------------------------------
### Detect Non-Writable USB Insertion
Source: https://context7.com/jamf/jamfprotect/llms.txt
This analytic identifies the insertion of removable storage devices that are marked as non-writable. It leverages USB event types to trigger alerts.
```yaml
name: UsbInsertionNonWritable
uuid: 28043b0a-7cd1-45b1-a128-148b96b7b6c7
inputType: GPUSBEvent
filter: >
$event.type == 0 AND
$event.device.removable == 1 AND
$event.device.writable != 1
actions:
- name: Log
```