Jamf Pro Hardening Guide
MDM hardening for Jamf Pro macOS and iOS device management
Overview
Jamf Pro is the leading Apple device management platform used by over 70,000 organizations to manage macOS, iOS, iPadOS, and tvOS devices. As a critical infrastructure component for device security, Jamf Pro configurations directly impact endpoint security posture across Apple fleets. Proper hardening ensures devices are configured securely while maintaining user productivity.
Intended Audience
- Security engineers managing Apple device fleets
- IT administrators configuring Jamf Pro
- GRC professionals assessing MDM security
- Third-party risk managers evaluating device management
How to Use This Guide
- L1 (Baseline): Essential controls for all organizations
- L2 (Hardened): Enhanced controls for security-sensitive environments
- L3 (Maximum Security): Strictest controls for regulated industries
Scope
This guide covers Jamf Pro server security, configuration profiles, CIS benchmark implementation, and managed device hardening.
Table of Contents
- Jamf Pro Server Security
- Device Security Policies
- CIS Benchmark Implementation
- Monitoring & Compliance
- Compliance Quick Reference
1. Jamf Pro Server Security
1.1 Secure Jamf Pro Console Access
Profile Level: L1 (Baseline)
| Framework | Control |
|---|---|
| CIS Controls | 5.4 |
| NIST 800-53 | AC-6(1) |
Description
Secure Jamf Pro console access with SSO, MFA, and role-based access controls.
Rationale
Why This Matters:
- Jamf Pro controls all managed device configurations
- Compromised admin can push malicious profiles/scripts
- Role-based access limits blast radius
ClickOps Implementation
Step 1: Configure SSO
- Navigate to: Jamf Pro → Settings → System → SSO
- Click Edit
- Configure SAML SSO:
- Upload IdP metadata
- Configure attribute mappings
- Set group mappings to Jamf roles
- Test SSO authentication
Step 2: Configure User Accounts
- Navigate to: Settings → User Accounts & Groups
- Review existing accounts
- Remove unnecessary admin accounts
- Convert local accounts to SSO where possible
Step 3: Configure Privilege Sets
- Navigate to: Settings → User Accounts → Privilege Sets
- Create granular privilege sets:
- Help Desk: Device lookup, basic actions
- Deployment: Profile and app management
- Security: Full security policy access
- Administrator: Full access (limit to 2-3)
- Assign minimum required privileges
Time to Complete: ~1 hour
Code Implementation
Code Pack: Terraform
# Create granular API roles for least-privilege access
# Help Desk role: device lookup and basic read-only actions
resource "jamfpro_api_role" "helpdesk" {
display_name = "HTH Help Desk"
privileges = toset(var.helpdesk_privileges)
}
# Deployment role: profile and app management
resource "jamfpro_api_role" "deployment" {
display_name = "HTH Deployment"
privileges = toset(var.deployment_privileges)
}
# Security role: full security policy access
resource "jamfpro_api_role" "security" {
display_name = "HTH Security"
privileges = toset(var.security_privileges)
}
# Dedicated Help Desk account with minimum privileges
resource "jamfpro_account" "helpdesk" {
name = "hth-helpdesk"
enabled = "Enabled"
access_level = "Full Access"
privilege_set = "Custom"
jss_objects_privileges = toset(var.helpdesk_privileges)
}
# Dedicated Deployment account
resource "jamfpro_account" "deployment" {
name = "hth-deployment"
enabled = "Enabled"
access_level = "Full Access"
privilege_set = "Custom"
jss_objects_privileges = toset(var.deployment_privileges)
}
1.2 Secure API Access
Profile Level: L1 (Baseline)
| Framework | Control |
|---|---|
| CIS Controls | 3.11 |
| NIST 800-53 | SC-12 |
Description
Secure Jamf Pro API access with dedicated accounts and token-based authentication.
ClickOps Implementation
Step 1: Create API Accounts
- Navigate to: Settings → User Accounts & Groups
- Create dedicated API accounts (not personal admin accounts)
- Assign minimum required privilege set
Step 2: Configure API Token Authentication
- Use bearer token authentication (not basic auth)
- Implement token rotation
- Monitor API usage
Step 3: Enable API Audit Logging
- Navigate to: Settings → Server → Logging
- Enable API request logging
- Export to SIEM for monitoring
Best Practices:
- Use separate API accounts per integration
- Store tokens in secure vault
- Set token expiration policies
- Audit API access regularly
Code Implementation
Code Pack: Terraform
# Dedicated API role with read-only privileges for security automation
resource "jamfpro_api_role" "security_automation" {
display_name = "HTH Security Automation"
privileges = toset(var.api_integration_privileges)
}
# API integration with dedicated OAuth2 credentials and scoped privileges
resource "jamfpro_api_integration" "security_automation" {
display_name = var.api_integration_name
enabled = true
authorization_scopes = toset(var.api_integration_privileges)
}
# L2: Restricted API integration with tighter token lifetime
resource "jamfpro_api_integration" "security_automation_hardened" {
count = var.profile_level >= 2 ? 1 : 0
display_name = "${var.api_integration_name} (Hardened)"
enabled = true
access_token_lifetime_seconds = 1800
authorization_scopes = toset(var.api_integration_privileges)
}
2. Device Security Policies
2.1 Configure Password Policies
Profile Level: L1 (Baseline)
| Framework | Control |
|---|---|
| CIS Controls | 5.2 |
| NIST 800-53 | IA-5 |
Description
Configure device password policies through configuration profiles.
ClickOps Implementation
Step 1: Create Password Policy Profile
- Navigate to: Computers → Configuration Profiles
- Click + New
- Select Password payload
- Configure:
- Minimum length: 12+ characters (14+ for L2)
- Require alphanumeric: Yes
- Minimum complex characters: 1+
- Maximum passcode age: 90 days (or disable for L3)
- Passcode history: Remember 5-10 passwords
- Auto-lock: 5 minutes (or less for L2)
Step 2: Scope Profile
- Configure scope:
- All computers
- Or specific groups
- Deploy profile
Time to Complete: ~20 minutes
Code Implementation
Code Pack: Terraform
# Password policy configuration profile for managed macOS devices
resource "jamfpro_macos_configuration_profile_plist" "password_policy" {
name = "HTH Password Policy (L${var.profile_level})"
description = "Device password policy per HTH hardening guide - Profile Level ${var.profile_level}"
distribution_method = "Install Automatically"
user_removable = false
level = "System"
redeploy_on_update = "All"
payloads = <<-XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadType</key>
<string>com.apple.mobiledevice.passwordpolicy</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadIdentifier</key>
<string>com.howtoharden.jamf.password</string>
<key>PayloadUUID</key>
<string>A1B2C3D4-E5F6-7890-ABCD-EF1234567890</string>
<key>PayloadDisplayName</key>
<string>Password Policy</string>
<key>minLength</key>
<integer>${var.password_min_length}</integer>
<key>requireAlphanumeric</key>
<true/>
<key>minComplexChars</key>
<integer>1</integer>
<key>maxPINAgeInDays</key>
<integer>${var.password_max_age_days}</integer>
<key>pinHistory</key>
<integer>${var.password_history_count}</integer>
<key>maxInactivity</key>
<integer>${var.auto_lock_minutes}</integer>
<key>maxFailedAttempts</key>
<integer>10</integer>
</dict>
</array>
<key>PayloadDisplayName</key>
<string>HTH Password Policy</string>
<key>PayloadIdentifier</key>
<string>com.howtoharden.jamf.password.profile</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>B2C3D4E5-F6A7-8901-BCDE-F12345678901</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>
XML
scope {
all_computers = var.scope_all_computers
computer_group_ids = var.scope_all_computers ? [] : toset(var.scope_computer_group_ids)
}
}
2.2 Configure FileVault Encryption
Profile Level: L1 (Baseline)
| Framework | Control |
|---|---|
| CIS Controls | 3.11 |
| NIST 800-53 | SC-28 |
Description
Enforce FileVault full disk encryption on all managed macOS devices.
Rationale
Why This Matters:
- Protects data on lost/stolen devices
- Required for most compliance frameworks
- Jamf can escrow recovery keys
ClickOps Implementation
Step 1: Create FileVault Profile
- Navigate to: Computers → Configuration Profiles
- Click + New
- Select Security & Privacy → FileVault payload
- Configure:
- Enable FileVault: Yes
- Defer to user: Enable (if not already encrypted)
- Recovery key type: Institutional or Personal
- Escrow to Jamf: Yes
Step 2: Configure Recovery Key Escrow
- Enable Escrow Personal Recovery Key
- Configure key rotation (optional)
- Store institutional key securely
Step 3: Create Remediation Policy
- Create Smart Group: Computers without FileVault
- Create policy to enforce or notify
Code Implementation
Code Pack: Terraform
# Disk encryption configuration for FileVault escrow
resource "jamfpro_disk_encryption_configuration" "filevault" {
name = "HTH FileVault Encryption"
key_type = "Individual"
file_vault_enabled_users = "Current or Next User"
}
# FileVault enforcement configuration profile
resource "jamfpro_macos_configuration_profile_plist" "filevault" {
name = "HTH FileVault Enforcement"
description = "Enforce FileVault full disk encryption per HTH hardening guide"
distribution_method = "Install Automatically"
user_removable = false
level = "System"
redeploy_on_update = "All"
payloads = <<-XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadType</key>
<string>com.apple.MCX.FileVault2</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadIdentifier</key>
<string>com.howtoharden.jamf.filevault</string>
<key>PayloadUUID</key>
<string>C3D4E5F6-A7B8-9012-CDEF-123456789012</string>
<key>PayloadDisplayName</key>
<string>FileVault 2</string>
<key>Enable</key>
<string>On</string>
<key>Defer</key>
<true/>
<key>ShowRecoveryKey</key>
<${var.filevault_escrow_enabled ? "false" : "true"}/>
<key>UseRecoveryKey</key>
<true/>
<key>UseKeychain</key>
<false/>
<key>DeferForceAtUserLoginMaxBypassAttempts</key>
<integer>3</integer>
<key>DeferDontAskAtUserLogout</key>
<false/>
</dict>
</array>
<key>PayloadDisplayName</key>
<string>HTH FileVault Enforcement</string>
<key>PayloadIdentifier</key>
<string>com.howtoharden.jamf.filevault.profile</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>D4E5F6A7-B8C9-0123-DEFA-234567890123</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>
XML
scope {
all_computers = var.scope_all_computers
computer_group_ids = var.scope_all_computers ? [] : toset(var.scope_computer_group_ids)
}
}
# Smart group to identify computers without FileVault enabled
resource "jamfpro_smart_computer_group" "filevault_not_enabled" {
name = "HTH - FileVault Not Enabled"
criteria {
name = "FileVault 2 Status"
search_type = "is not"
value = "Encrypted"
priority = 0
}
}
2.3 Configure Firewall
Profile Level: L1 (Baseline)
| Framework | Control |
|---|---|
| CIS Controls | 4.4 |
| NIST 800-53 | SC-7 |
Description
Enable and configure macOS firewall on all managed devices.
ClickOps Implementation
Step 1: Create Firewall Profile
- Navigate to: Computers → Configuration Profiles
- Click + New
- Select Security & Privacy → Firewall payload
- Configure:
- Enable firewall: Yes
- Block all incoming connections: No (or Yes for L3)
- Enable stealth mode: Yes
- Automatically allow built-in software: Yes
- Automatically allow signed software: Yes (or No for L3)
Step 2: Scope and Deploy
- Scope to all computers
- Deploy profile
Code Implementation
Code Pack: Terraform
# macOS firewall configuration profile
resource "jamfpro_macos_configuration_profile_plist" "firewall" {
name = "HTH Firewall Policy (L${var.profile_level})"
description = "Enable and configure macOS firewall per HTH hardening guide"
distribution_method = "Install Automatically"
user_removable = false
level = "System"
redeploy_on_update = "All"
payloads = <<-XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadType</key>
<string>com.apple.security.firewall</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadIdentifier</key>
<string>com.howtoharden.jamf.firewall</string>
<key>PayloadUUID</key>
<string>E5F6A7B8-C9D0-1234-EFAB-345678901234</string>
<key>PayloadDisplayName</key>
<string>Firewall</string>
<key>EnableFirewall</key>
<true/>
<key>BlockAllIncoming</key>
<${var.firewall_block_all_incoming || var.profile_level >= 3 ? "true" : "false"}/>
<key>EnableStealthMode</key>
<${var.firewall_stealth_mode ? "true" : "false"}/>
<key>AllowSigned</key>
<${var.profile_level >= 3 ? "false" : "true"}/>
<key>AllowSignedApp</key>
<${var.profile_level >= 3 ? "false" : "true"}/>
</dict>
</array>
<key>PayloadDisplayName</key>
<string>HTH Firewall Policy</string>
<key>PayloadIdentifier</key>
<string>com.howtoharden.jamf.firewall.profile</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>F6A7B8C9-D0E1-2345-FABC-456789012345</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>
XML
scope {
all_computers = var.scope_all_computers
computer_group_ids = var.scope_all_computers ? [] : toset(var.scope_computer_group_ids)
}
}
# Smart group to identify computers with firewall disabled
resource "jamfpro_smart_computer_group" "firewall_disabled" {
name = "HTH - Firewall Disabled"
criteria {
name = "Firewall Status"
search_type = "is not"
value = "On"
priority = 0
}
}
2.4 Configure Software Updates
Profile Level: L1 (Baseline)
| Framework | Control |
|---|---|
| CIS Controls | 7.3 |
| NIST 800-53 | SI-2 |
Description
Configure automatic software updates and patch management.
ClickOps Implementation
Step 1: Create Software Update Profile
- Navigate to: Computers → Configuration Profiles
- Click + New
- Select Software Update payload
- Configure:
- Automatic update check: Yes
- Download updates in background: Yes
- Install app updates: Yes
- Install macOS updates: Yes
- Install security responses: Yes (Critical)
Step 2: Configure Update Deferral (L2)
- For controlled environments:
- Defer major OS updates: 30-90 days
- Defer security updates: 0-7 days (test first)
Step 3: Create Patch Policies
- Use Jamf Pro patch management
- Configure automatic patching for critical apps
- Monitor patch compliance
Code Implementation
Code Pack: Terraform
# Automatic software update configuration profile
resource "jamfpro_macos_configuration_profile_plist" "software_updates" {
name = "HTH Software Update Policy (L${var.profile_level})"
description = "Configure automatic software updates per HTH hardening guide"
distribution_method = "Install Automatically"
user_removable = false
level = "System"
redeploy_on_update = "All"
payloads = <<-XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadType</key>
<string>com.apple.SoftwareUpdate</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadIdentifier</key>
<string>com.howtoharden.jamf.softwareupdate</string>
<key>PayloadUUID</key>
<string>A7B8C9D0-E1F2-3456-ABCD-567890123456</string>
<key>PayloadDisplayName</key>
<string>Software Update</string>
<key>AutomaticCheckEnabled</key>
<true/>
<key>AutomaticDownload</key>
<true/>
<key>AutomaticallyInstallAppUpdates</key>
<true/>
<key>AutomaticallyInstallMacOSUpdates</key>
<true/>
<key>CriticalUpdateInstall</key>
<true/>
<key>ConfigDataInstall</key>
<true/>
</dict>
</array>
<key>PayloadDisplayName</key>
<string>HTH Software Update Policy</string>
<key>PayloadIdentifier</key>
<string>com.howtoharden.jamf.softwareupdate.profile</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>B8C9D0E1-F2A3-4567-BCDE-678901234567</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>
XML
scope {
all_computers = var.scope_all_computers
computer_group_ids = var.scope_all_computers ? [] : toset(var.scope_computer_group_ids)
}
}
# L2: Software update deferral profile for controlled environments
resource "jamfpro_macos_configuration_profile_plist" "software_update_deferral" {
count = var.profile_level >= 2 ? 1 : 0
name = "HTH Software Update Deferral (L${var.profile_level})"
description = "Defer OS updates for testing before deployment per HTH hardening guide"
distribution_method = "Install Automatically"
user_removable = false
level = "System"
redeploy_on_update = "All"
payloads = <<-XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadType</key>
<string>com.apple.applicationaccess</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadIdentifier</key>
<string>com.howtoharden.jamf.updatedeferral</string>
<key>PayloadUUID</key>
<string>C9D0E1F2-A3B4-5678-CDEF-789012345678</string>
<key>PayloadDisplayName</key>
<string>Software Update Deferral</string>
<key>enforcedSoftwareUpdateDelay</key>
<integer>${var.software_update_deferral_days}</integer>
<key>enforcedSoftwareUpdateMajorOSDeferredInstallDelay</key>
<integer>${var.software_update_deferral_days}</integer>
<key>enforcedSoftwareUpdateMinorOSDeferredInstallDelay</key>
<integer>${var.security_update_deferral_days}</integer>
<key>enforcedSoftwareUpdateNonOSDeferredInstallDelay</key>
<integer>${var.security_update_deferral_days}</integer>
<key>forceDelayedSoftwareUpdates</key>
<true/>
<key>forceDelayedMajorSoftwareUpdates</key>
<true/>
</dict>
</array>
<key>PayloadDisplayName</key>
<string>HTH Software Update Deferral</string>
<key>PayloadIdentifier</key>
<string>com.howtoharden.jamf.updatedeferral.profile</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>D0E1F2A3-B4C5-6789-DEFA-890123456789</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>
XML
scope {
all_computers = var.scope_all_computers
computer_group_ids = var.scope_all_computers ? [] : toset(var.scope_computer_group_ids)
}
}
# Smart group to identify computers with outdated OS
resource "jamfpro_smart_computer_group" "os_not_current" {
name = "HTH - OS Not Current"
criteria {
name = "Operating System Version"
search_type = "less than"
value = "15.0"
priority = 0
}
}
3. CIS Benchmark Implementation
3.1 Deploy CIS Benchmark Profiles
Profile Level: L2 (Hardened)
| Framework | Control |
|---|---|
| CIS Controls | 4.1 |
| NIST 800-53 | CM-6 |
Description
Deploy CIS macOS Benchmark security configurations using Jamf Compliance Editor.
Rationale
Why This Matters:
- CIS Benchmarks are industry-standard security baselines
- Jamf Compliance Editor generates MDM-ready profiles
- Supports both Level 1 and Level 2 hardening
Prerequisites
- Download Jamf Compliance Editor from GitHub
- Review CIS macOS Benchmark requirements
- Test in non-production environment
Implementation
Step 1: Generate CIS Profiles
- Download Jamf Compliance Editor
- Open application
- Select CIS Benchmark level:
- Level 1: Baseline security
- Level 2: Enhanced security
- Generate configuration profiles
Step 2: Review and Customize
- Review generated settings
- Customize based on business needs
- Document any deviations
Step 3: Deploy Profiles
- Upload profiles to Jamf Pro
- Create test scope first
- Validate functionality
- Deploy to production
Key CIS Controls:
| CIS Control | Description | Implementation |
|---|---|---|
| 2.1.1 | Enable FileVault | Configuration Profile |
| 2.1.2 | Enable Gatekeeper | Configuration Profile |
| 2.3.1 | Set screen saver | Configuration Profile |
| 2.5.1 | Enable Firewall | Configuration Profile |
| 3.3 | Disable Remote Login | Configuration Profile |
Time to Complete: ~4 hours (testing included)
Code Implementation
Code Pack: Terraform
# CIS 2.1.2 - Gatekeeper enforcement profile (L2+)
resource "jamfpro_macos_configuration_profile_plist" "cis_gatekeeper" {
count = var.profile_level >= 2 ? 1 : 0
name = "HTH CIS - Gatekeeper Enforcement"
description = "CIS macOS Benchmark 2.1.2: Enable Gatekeeper"
distribution_method = "Install Automatically"
user_removable = false
level = "System"
redeploy_on_update = "All"
payloads = <<-XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadType</key>
<string>com.apple.systempolicy.control</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadIdentifier</key>
<string>com.howtoharden.jamf.cis.gatekeeper</string>
<key>PayloadUUID</key>
<string>E1F2A3B4-C5D6-7890-EFAB-901234567890</string>
<key>PayloadDisplayName</key>
<string>Gatekeeper</string>
<key>AllowIdentifiedDevelopers</key>
<${var.cis_gatekeeper_enabled ? "true" : "false"}/>
<key>EnableAssessment</key>
<true/>
</dict>
</array>
<key>PayloadDisplayName</key>
<string>HTH CIS Gatekeeper</string>
<key>PayloadIdentifier</key>
<string>com.howtoharden.jamf.cis.gatekeeper.profile</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>F2A3B4C5-D6E7-8901-FABC-012345678901</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>
XML
scope {
all_computers = var.scope_all_computers
computer_group_ids = var.scope_all_computers ? [] : toset(var.scope_computer_group_ids)
}
}
# CIS 2.3.1 - Screen saver idle time profile (L2+)
resource "jamfpro_macos_configuration_profile_plist" "cis_screensaver" {
count = var.profile_level >= 2 ? 1 : 0
name = "HTH CIS - Screen Saver Idle Time"
description = "CIS macOS Benchmark 2.3.1: Set screen saver idle time"
distribution_method = "Install Automatically"
user_removable = false
level = "System"
redeploy_on_update = "All"
payloads = <<-XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadType</key>
<string>com.apple.screensaver</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadIdentifier</key>
<string>com.howtoharden.jamf.cis.screensaver</string>
<key>PayloadUUID</key>
<string>A3B4C5D6-E7F8-9012-ABCD-123456789012</string>
<key>PayloadDisplayName</key>
<string>Screen Saver</string>
<key>idleTime</key>
<integer>${var.cis_screen_saver_idle_time}</integer>
<key>askForPassword</key>
<true/>
<key>askForPasswordDelay</key>
<integer>0</integer>
</dict>
</array>
<key>PayloadDisplayName</key>
<string>HTH CIS Screen Saver</string>
<key>PayloadIdentifier</key>
<string>com.howtoharden.jamf.cis.screensaver.profile</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>B4C5D6E7-F8A9-0123-BCDE-234567890123</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>
XML
scope {
all_computers = var.scope_all_computers
computer_group_ids = var.scope_all_computers ? [] : toset(var.scope_computer_group_ids)
}
}
# CIS 3.3 - Disable Remote Login (SSH) profile (L2+)
resource "jamfpro_macos_configuration_profile_plist" "cis_disable_remote_login" {
count = var.profile_level >= 2 && var.cis_disable_remote_login ? 1 : 0
name = "HTH CIS - Disable Remote Login"
description = "CIS macOS Benchmark 3.3: Disable Remote Login (SSH)"
distribution_method = "Install Automatically"
user_removable = false
level = "System"
redeploy_on_update = "All"
payloads = <<-XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadType</key>
<string>com.apple.MCX</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadIdentifier</key>
<string>com.howtoharden.jamf.cis.remotelogin</string>
<key>PayloadUUID</key>
<string>C5D6E7F8-A9B0-1234-CDEF-345678901234</string>
<key>PayloadDisplayName</key>
<string>Disable Remote Login</string>
<key>com.apple.access_ssh</key>
<array/>
</dict>
</array>
<key>PayloadDisplayName</key>
<string>HTH CIS Disable Remote Login</string>
<key>PayloadIdentifier</key>
<string>com.howtoharden.jamf.cis.remotelogin.profile</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>D6E7F8A9-B0C1-2345-DEFA-456789012345</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>
XML
scope {
all_computers = var.scope_all_computers
computer_group_ids = var.scope_all_computers ? [] : toset(var.scope_computer_group_ids)
}
}
3.2 Monitor CIS Compliance
Profile Level: L2 (Hardened)
| Framework | Control |
|---|---|
| CIS Controls | 4.1 |
| NIST 800-53 | CA-7 |
Description
Monitor device compliance with CIS Benchmark configurations.
ClickOps Implementation
Step 1: Create Smart Groups
- Create smart groups for compliance monitoring:
- FileVault not enabled
- Firewall not enabled
- OS not updated
- Non-compliant configurations
Step 2: Configure Compliance Reporting
- Navigate to: Settings → Computer Management → Extension Attributes
- Create extension attributes for CIS checks
- Use in smart groups for compliance tracking
Step 3: Generate Compliance Reports
- Use Jamf Pro reporting
- Export for compliance documentation
- Track compliance trends
Code Implementation
Code Pack: Terraform
# Extension attribute to track CIS compliance status via script (L2+)
resource "jamfpro_computer_extension_attribute" "cis_compliance" {
count = var.profile_level >= 2 ? 1 : 0
name = "HTH CIS Compliance Status"
description = "Reports CIS macOS Benchmark compliance status"
data_type = "String"
enabled = true
input_type = "SCRIPT"
inventory_display_type = "EXTENSION_ATTRIBUTES"
script_contents = var.cis_compliance_ea_script != "" ? var.cis_compliance_ea_script : <<-BASH
#!/bin/bash
# HTH CIS Compliance Check Extension Attribute
# Reports: Compliant, Non-Compliant, or Partial
ISSUES=0
CHECKS=0
# Check FileVault (CIS 2.1.1)
CHECKS=$((CHECKS + 1))
FV_STATUS=$(fdesetup status 2>/dev/null)
if [[ "$FV_STATUS" != *"FileVault is On"* ]]; then
ISSUES=$((ISSUES + 1))
fi
# Check Firewall (CIS 2.5.1)
CHECKS=$((CHECKS + 1))
FW_STATUS=$(/usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate 2>/dev/null)
if [[ "$FW_STATUS" != *"enabled"* ]]; then
ISSUES=$((ISSUES + 1))
fi
# Check Gatekeeper (CIS 2.1.2)
CHECKS=$((CHECKS + 1))
GK_STATUS=$(spctl --status 2>/dev/null)
if [[ "$GK_STATUS" != *"assessments enabled"* ]]; then
ISSUES=$((ISSUES + 1))
fi
# Check Remote Login disabled (CIS 3.3)
CHECKS=$((CHECKS + 1))
SSH_STATUS=$(systemsetup -getremotelogin 2>/dev/null)
if [[ "$SSH_STATUS" == *"On"* ]]; then
ISSUES=$((ISSUES + 1))
fi
# Report result
if [ $ISSUES -eq 0 ]; then
echo "<result>Compliant</result>"
elif [ $ISSUES -lt $CHECKS ]; then
echo "<result>Partial ($((CHECKS - ISSUES))/$CHECKS passed)</result>"
else
echo "<result>Non-Compliant</result>"
fi
BASH
}
# Smart group: CIS non-compliant computers (L2+)
resource "jamfpro_smart_computer_group" "cis_non_compliant" {
count = var.profile_level >= 2 ? 1 : 0
name = "HTH - CIS Non-Compliant"
criteria {
name = "HTH CIS Compliance Status"
search_type = "is not"
value = "Compliant"
priority = 0
}
}
# Smart group: FileVault not enabled (L2+)
resource "jamfpro_smart_computer_group" "cis_filevault_missing" {
count = var.profile_level >= 2 ? 1 : 0
name = "HTH - CIS FileVault Not Enabled"
criteria {
name = "FileVault 2 Status"
search_type = "is not"
value = "Encrypted"
priority = 0
}
}
# Smart group: OS not updated (L2+)
resource "jamfpro_smart_computer_group" "cis_os_outdated" {
count = var.profile_level >= 2 ? 1 : 0
name = "HTH - CIS OS Not Updated"
criteria {
name = "Number of Available Updates"
search_type = "more than"
value = "0"
priority = 0
}
}
4. Monitoring & Compliance
4.1 Enable Audit Logging
Profile Level: L1 (Baseline)
| Framework | Control |
|---|---|
| CIS Controls | 8.2 |
| NIST 800-53 | AU-2 |
Description
Enable comprehensive audit logging for Jamf Pro activities.
ClickOps Implementation
Step 1: Configure Server Logs
- Navigate to: Settings → Server → Logging
- Configure log retention
- Enable all relevant log types
Step 2: Configure SIEM Integration
- Configure syslog forwarding to SIEM
- Or use Jamf Pro webhooks for events
- Monitor for security events
Code Implementation
Code Pack: Terraform
# Webhook for admin login events to SIEM
resource "jamfpro_webhook" "admin_login" {
count = var.siem_webhook_url != "" ? 1 : 0
name = "HTH Audit - Admin Login"
enabled = true
url = var.siem_webhook_url
content_type = "application/json"
event = "JSSLoginSuccess"
connection_timeout = 5
read_timeout = 10
authentication_type = var.siem_webhook_auth_type
username = var.siem_webhook_username
password = var.siem_webhook_password
}
# Webhook for computer policy changes
resource "jamfpro_webhook" "policy_change" {
count = var.siem_webhook_url != "" ? 1 : 0
name = "HTH Audit - Policy Change"
enabled = true
url = var.siem_webhook_url
content_type = "application/json"
event = "PolicyFinished"
connection_timeout = 5
read_timeout = 10
authentication_type = var.siem_webhook_auth_type
username = var.siem_webhook_username
password = var.siem_webhook_password
}
# Webhook for computer enrollment events
resource "jamfpro_webhook" "computer_enrollment" {
count = var.siem_webhook_url != "" ? 1 : 0
name = "HTH Audit - Computer Enrollment"
enabled = true
url = var.siem_webhook_url
content_type = "application/json"
event = "ComputerAdded"
connection_timeout = 5
read_timeout = 10
authentication_type = var.siem_webhook_auth_type
username = var.siem_webhook_username
password = var.siem_webhook_password
}
# Webhook for computer check-in events (L2+)
resource "jamfpro_webhook" "computer_checkin" {
count = var.siem_webhook_url != "" && var.profile_level >= 2 ? 1 : 0
name = "HTH Audit - Computer Check-In"
enabled = true
url = var.siem_webhook_url
content_type = "application/json"
event = "ComputerCheckIn"
connection_timeout = 5
read_timeout = 10
authentication_type = var.siem_webhook_auth_type
username = var.siem_webhook_username
password = var.siem_webhook_password
}
# Webhook for push command sent events (L2+)
resource "jamfpro_webhook" "push_sent" {
count = var.siem_webhook_url != "" && var.profile_level >= 2 ? 1 : 0
name = "HTH Audit - Push Sent"
enabled = true
url = var.siem_webhook_url
content_type = "application/json"
event = "PushSent"
connection_timeout = 5
read_timeout = 10
authentication_type = var.siem_webhook_auth_type
username = var.siem_webhook_username
password = var.siem_webhook_password
}
# Webhook for mobile device enrollment events (L2+)
resource "jamfpro_webhook" "mobile_enrollment" {
count = var.siem_webhook_url != "" && var.profile_level >= 2 ? 1 : 0
name = "HTH Audit - Mobile Device Enrollment"
enabled = true
url = var.siem_webhook_url
content_type = "application/json"
event = "MobileDeviceEnrolled"
connection_timeout = 5
read_timeout = 10
authentication_type = var.siem_webhook_auth_type
username = var.siem_webhook_username
password = var.siem_webhook_password
}
4.2 Key Events to Monitor
| Event | Detection Use Case |
|---|---|
| Admin login | Unauthorized access |
| Profile deployment | Unauthorized configuration |
| Script execution | Malicious scripts |
| Policy changes | Configuration drift |
| Failed enrollment | Enrollment issues |
| MDM command | Unauthorized commands |
5. Compliance Quick Reference
SOC 2 Trust Services Criteria Mapping
| Control ID | Jamf Control | Guide Section |
|---|---|---|
| CC6.1 | Console access control | 1.1 |
| CC6.6 | Device policies | 2.1 |
| CC6.7 | Encryption | 2.2 |
| CC7.1 | Firewall | 2.3 |
| CC7.2 | Audit logging | 4.1 |
NIST 800-53 Rev 5 Mapping
| Control | Jamf Control | Guide Section |
|---|---|---|
| AC-6(1) | Admin roles | 1.1 |
| IA-5 | Password policy | 2.1 |
| SC-28 | FileVault encryption | 2.2 |
| SC-7 | Firewall | 2.3 |
| CM-6 | CIS Benchmarks | 3.1 |
Appendix A: Plan Compatibility
| Feature | Jamf Now | Jamf Pro | Jamf Connect |
|---|---|---|---|
| Configuration Profiles | Basic | Full | N/A |
| Scripts | ❌ | ✅ | N/A |
| Extension Attributes | ❌ | ✅ | N/A |
| Patch Management | ❌ | ✅ | N/A |
| SSO | ❌ | ✅ | N/A |
| API Access | Limited | Full | N/A |
| SIEM Integration | ❌ | ✅ | N/A |
Appendix B: References
Official Jamf Documentation:
- Trust Center - Compliance
- Security Portal
- Information Security
- Product Documentation
- Jamf Pro Documentation
- Jamf Pro Security Recommendations
- Third-Party Audits
API & Developer Tools:
Compliance Frameworks:
- SOC 2 Type II, ISO 27001, ISO 27701 – via Trust Center and Security Portal
Security Incidents:
- No major public security incidents affecting Jamf’s hosted infrastructure identified as of February 2026. Product-level CVEs (e.g., broken access control in Jamf Pro Server before 10.46.1) have been patched through standard release cycles.
Community Resources:
Changelog
| Date | Version | Maturity | Changes | Author |
|---|---|---|---|---|
| 2025-02-05 | 0.1.0 | draft | Initial guide with server security, device policies, and CIS implementation | Claude Code (Opus 4.5) |
Contributing
Found an issue or want to improve this guide?
- Report outdated information: Open an issue with tag
content-outdated - Propose new controls: Open an issue with tag
new-control - Submit improvements: See Contributing Guide