v0.1.0-draft AI Drafted

Jamf Pro Hardening Guide

IT Operations Last updated: 2025-02-05

MDM hardening for Jamf Pro macOS and iOS device management

Code Packs: Terraform

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

  1. Jamf Pro Server Security
  2. Device Security Policies
  3. CIS Benchmark Implementation
  4. Monitoring & Compliance
  5. 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

  1. Navigate to: Jamf ProSettingsSystemSSO
  2. Click Edit
  3. Configure SAML SSO:
    • Upload IdP metadata
    • Configure attribute mappings
    • Set group mappings to Jamf roles
  4. Test SSO authentication

Step 2: Configure User Accounts

  1. Navigate to: SettingsUser Accounts & Groups
  2. Review existing accounts
  3. Remove unnecessary admin accounts
  4. Convert local accounts to SSO where possible

Step 3: Configure Privilege Sets

  1. Navigate to: SettingsUser AccountsPrivilege Sets
  2. 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)
  3. Assign minimum required privileges

Time to Complete: ~1 hour

Code Implementation

Code Pack: Terraform
hth-jamf-1.01-secure-console-access.tf View source on GitHub ↗
# 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

  1. Navigate to: SettingsUser Accounts & Groups
  2. Create dedicated API accounts (not personal admin accounts)
  3. Assign minimum required privilege set

Step 2: Configure API Token Authentication

  1. Use bearer token authentication (not basic auth)
  2. Implement token rotation
  3. Monitor API usage

Step 3: Enable API Audit Logging

  1. Navigate to: SettingsServerLogging
  2. Enable API request logging
  3. 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
hth-jamf-1.02-secure-api-access.tf View source on GitHub ↗
# 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

  1. Navigate to: ComputersConfiguration Profiles
  2. Click + New
  3. Select Password payload
  4. 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

  1. Configure scope:
    • All computers
    • Or specific groups
  2. Deploy profile

Time to Complete: ~20 minutes

Code Implementation

Code Pack: Terraform
hth-jamf-2.01-configure-password-policies.tf View source on GitHub ↗
# 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

  1. Navigate to: ComputersConfiguration Profiles
  2. Click + New
  3. Select Security & PrivacyFileVault payload
  4. 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

  1. Enable Escrow Personal Recovery Key
  2. Configure key rotation (optional)
  3. Store institutional key securely

Step 3: Create Remediation Policy

  1. Create Smart Group: Computers without FileVault
  2. Create policy to enforce or notify

Code Implementation

Code Pack: Terraform
hth-jamf-2.02-configure-filevault-encryption.tf View source on GitHub ↗
# 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

  1. Navigate to: ComputersConfiguration Profiles
  2. Click + New
  3. Select Security & PrivacyFirewall payload
  4. 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

  1. Scope to all computers
  2. Deploy profile

Code Implementation

Code Pack: Terraform
hth-jamf-2.03-configure-firewall.tf View source on GitHub ↗
# 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

  1. Navigate to: ComputersConfiguration Profiles
  2. Click + New
  3. Select Software Update payload
  4. 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)

  1. For controlled environments:
    • Defer major OS updates: 30-90 days
    • Defer security updates: 0-7 days (test first)

Step 3: Create Patch Policies

  1. Use Jamf Pro patch management
  2. Configure automatic patching for critical apps
  3. Monitor patch compliance

Code Implementation

Code Pack: Terraform
hth-jamf-2.04-configure-software-updates.tf View source on GitHub ↗
# 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

  1. Download Jamf Compliance Editor
  2. Open application
  3. Select CIS Benchmark level:
    • Level 1: Baseline security
    • Level 2: Enhanced security
  4. Generate configuration profiles

Step 2: Review and Customize

  1. Review generated settings
  2. Customize based on business needs
  3. Document any deviations

Step 3: Deploy Profiles

  1. Upload profiles to Jamf Pro
  2. Create test scope first
  3. Validate functionality
  4. 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
hth-jamf-3.01-deploy-cis-benchmark-profiles.tf View source on GitHub ↗
# 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

  1. Create smart groups for compliance monitoring:
    • FileVault not enabled
    • Firewall not enabled
    • OS not updated
    • Non-compliant configurations

Step 2: Configure Compliance Reporting

  1. Navigate to: SettingsComputer ManagementExtension Attributes
  2. Create extension attributes for CIS checks
  3. Use in smart groups for compliance tracking

Step 3: Generate Compliance Reports

  1. Use Jamf Pro reporting
  2. Export for compliance documentation
  3. Track compliance trends

Code Implementation

Code Pack: Terraform
hth-jamf-3.02-monitor-cis-compliance.tf View source on GitHub ↗
# 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

  1. Navigate to: SettingsServerLogging
  2. Configure log retention
  3. Enable all relevant log types

Step 2: Configure SIEM Integration

  1. Configure syslog forwarding to SIEM
  2. Or use Jamf Pro webhooks for events
  3. Monitor for security events

Code Implementation

Code Pack: Terraform
hth-jamf-4.01-enable-audit-logging.tf View source on GitHub ↗
# 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:

API & Developer Tools:

Compliance Frameworks:

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?