Jamf Concepts

Guides

Managing the Jamf Platform with Terraform: the Jamf Platform provider

~11 min read
Was this helpful?

This guide is a self-contained introduction to managing your Jamf environment with Terraform using the Jamf Platform provider. You don't need to have read anything else first — we'll cover installation, a project skeleton and the resources you'll most likely want to define. If you're brand new to Infrastructure as Code, our introduction to managing Jamf with Terraform is worth a read alongside this one, but it isn't a prerequisite.

The Jamf Platform APIs power a generation of Jamf microservices — blueprints, Compliance Benchmarks, device groups and more — that sit alongside Jamf Pro and Jamf Protect. We've built a dedicated Terraform provider for them, and as of recent releases that same provider now also federates the full Jamf Pro surface, so a single provider and a single set of credentials can describe almost your entire Jamf environment as code.

Public beta. The Jamf Platform API is currently in public beta. Provider stability, functionality and schemas are subject to change without notice.

A nod to where this started

This provider builds on a path charted by Deployment Theory and their terraform-provider-jamfpro. First released in early 2024, it grew into the most comprehensive community Terraform provider for Jamf and the one the community rallies behind. It proved the depth of demand for managing Jamf as code and set the bar for what a Jamf provider could be — this provider wouldn't exist in its current form without that groundwork. terraform-provider-jamfpro remains an independent, community-maintained project; we're grateful to its maintainers for the example they set.

What does it cover?

The provider groups resources by the Jamf product they target, all behind one provider configuration:

Native Jamf Platform Services — continuously deployed microservices with no tenant version requirement:

  • Compliance Benchmark Engine — create and manage benchmarks against macOS Security Compliance Project (mSCP) baselines including CIS Level 1 and 2, NIST 800-53 (Low, Moderate, High) and DISA STIG. Enable or disable individual rules, customize Organization-Defined Values (ODV) and target benchmarks at specific device groups.
  • Blueprints — define device configuration covering software update enforcement and settings, passcode policies, Safari settings, disk management, legacy payloads, custom DDM declarations and more. Each blueprint targets one or more device groups and can be deployed or undeployed as code.
  • Device Groups — smart and static groups for computers and mobile devices, used to target benchmarks and blueprints.
  • Unified Inventory and Device Actions — read-only data sources for querying devices, plus Terraform 1.14+ actions for erase, restart, shutdown and unmanage.

The Jamf Pro surface — under the jamfplatform_pro_* namespace, the provider now covers the breadth of Jamf Pro: policies, scripts, configuration profiles (macOS and mobile), Smart and Static groups, computer and mobile prestages, extension attributes, packages, categories, buildings, departments, LDAP servers, printers, restricted software, patch management, PKI configurations, and the many singleton settings screens — along with their data sources, list resources and management actions.

All told, the provider ships well over 90 resources, 120+ data sources, 60+ list resources and 20+ actions across both namespaces, and every resource supports full CRUD and terraform import. The jamfplatform_pro_* resources are built against the Jamf Pro API as of version 11.29.0; tenants below that emit an advisory warning, and resources that depend on newer endpoints declare their own minimum and fail clearly on unsupported tenants. For the authoritative, always-current list, see the provider documentation.

Before you begin

You'll need Terraform (or OpenTofu) and a way to talk to your tenant.

Install Terraform. On macOS the quickest route is Homebrew:

brew tap hashicorp/tap
brew install hashicorp/tap/terraform
terraform -version    # confirm it installed

The provider requires Terraform >= 1.13.0 (or OpenTofu >= 1.6.0). A couple of features — list resources and device actions — need Terraform 1.14+.

A text editor. Any editor works; we like VS Code with the official HashiCorp Terraform extension for syntax highlighting and validation.

API credentials. Head to Jamf Account and create an API client with the permissions you need for the resources you want to manage. The Getting Started guide on the Jamf Developer portal walks through this. The same OAuth client and tenant ID work across both the native Platform and jamfplatform_pro_* resources.

Configuring the provider

Create a directory for your project and add a terraform block declaring the provider, plus a provider block:

terraform {
  required_providers {
    jamfplatform = {
      source  = "Jamf-Concepts/jamfplatform"
      version = "~> 0.19.0"
    }
  }
}

provider "jamfplatform" {
  base_url      = var.jamfplatform_base_url
  client_id     = var.jamfplatform_client_id
  client_secret = var.jamfplatform_client_secret
  tenant_id     = var.jamfplatform_tenant_id
}

Use the API gateway for your region as the base_url: https://us.apigw.jamf.com, https://eu.apigw.jamf.com or https://apac.apigw.jamf.com. You can also keep the provider block clean with environment variables:

export JAMFPLATFORM_BASE_URL="https://us.apigw.jamf.com"
export JAMFPLATFORM_CLIENT_ID="your-client-id"
export JAMFPLATFORM_CLIENT_SECRET="your-client-secret"
export JAMFPLATFORM_TENANT_ID="your-tenant-id"

As always, keep credentials out of Git. Put variable values in a terraform.tfvars file and add it to your .gitignore. Terraform records what it manages in a terraform.tfstate file — keep one state per environment so they never cross.

Don't test this against a production instance. Terraform is a powerful tool; use a sandbox or beta tenant while you find your feet.

Defining your resources

Let's walk through some high-impact examples.

Device groups

Before creating benchmarks or blueprints you'll want a device group to target them at. Here's a smart computer group for devices running macOS 26 or later:

resource "jamfplatform_device_group" "macos_26_plus" {
  name        = "macOS 26+"
  group_type  = "smart"
  device_type = "computer"
  criteria = [
    {
      criteria = "Operating System Version"
      operator = "greater than or equal"
      value    = "26.0"
    }
  ]
}

This group can then be referenced by ID in your benchmark and blueprint resources, so Terraform understands the dependency graph and creates everything in the right order.

Compliance benchmarks

Compliance benchmarks use the mSCP baselines built into the Compliance Benchmark Engine. First, use a data source to fetch the rules for the baseline you want. Then create a benchmark that references those rules and targets your device group:

data "jamfplatform_cbengine_rules" "cis_lvl1" {
  baseline_id = "cis_lvl1"
}

resource "jamfplatform_cbengine_benchmark" "cis_level_1" {
  title              = "CIS macOS Level 1"
  description        = "CIS Level 1 benchmark - Managed by Terraform"
  source_baseline_id = "cis_lvl1"

  sources = [
    for s in data.jamfplatform_cbengine_rules.cis_lvl1.sources : {
      branch   = s.branch
      revision = s.revision
    }
  ]

  rules = [
    for r in data.jamfplatform_cbengine_rules.cis_lvl1.rules : {
      id      = r.id
      enabled = r.enabled
    }
  ]

  target_device_group = jamfplatform_device_group.macos_26_plus.id
  enforcement_mode    = "MONITOR"
}

This creates a CIS Level 1 benchmark in monitor mode, scoped to the smart group we defined earlier. Every rule from the baseline is included, and you can selectively disable individual rules or set custom ODV values to match your organization's requirements:

rules = [
  {
    id        = "system_settings_time_server_configure"
    enabled   = true
    odv_value = "ntp.example.com"
  },
  {
    id      = "system_settings_critical_update_install_enforce"
    enabled = true
  }
]

Setting enforcement_mode to "MONITOR_AND_ENFORCE" will remediate non-compliant settings automatically.

It's worth noting that the Compliance Benchmark Engine API does not currently offer a PUT or PATCH method for benchmarks. This means any change to a benchmark resource in your configuration results in a destroy-and-recreate (force replacement) rather than an in-place update. Keep this in mind when changing benchmarks that are already deployed and actively reporting on devices.

The real power here is that you can version-control the exact set of rules and customizations you've applied, review changes through pull requests and replicate the same benchmark across multiple tenants with zero manual effort.

Blueprints

Blueprints compose multiple configuration components into a single unit that gets deployed to one or more device groups. Each component type maps to a Declarative Device Management (DDM) declaration configuration domain. Here's a blueprint that configures software update settings:

resource "jamfplatform_blueprints_blueprint" "software_update_settings" {
  name        = "Software Update Settings"
  description = "Managed by Terraform"
  deployed    = true

  device_groups = [jamfplatform_device_group.macos_26_plus.id]

  software_update_settings = {
    allow_standard_user_os_updates           = true
    automatic_download                       = "AlwaysOn"
    automatic_install_os_updates             = "AlwaysOn"
    automatic_install_security_updates       = "AlwaysOn"
    deferral_major_period_days               = 30
    deferral_minor_period_days               = 14
    deferral_system_period_days              = 3
    notifications_enabled                    = true
    rapid_security_response_enabled          = true
    rapid_security_response_rollback_enabled = false
    recommended_cadence                      = "Newest"
  }
}

And here's one that enforces a passcode policy:

resource "jamfplatform_blueprints_blueprint" "passcode_policy" {
  name        = "Passcode Policy"
  description = "Managed by Terraform"
  deployed    = true

  device_groups = [jamfplatform_device_group.macos_26_plus.id]

  passcode_policy = {
    require_passcode              = true
    require_alphanumeric_passcode = true
    minimum_length                = 12
    minimum_complex_characters    = 1
    maximum_failed_attempts       = 10
    maximum_inactivity_in_minutes = 5
    maximum_passcode_age_in_days  = 90
    passcode_reuse_limit          = 5
  }
}

Each blueprint targets a set of device groups via device_groups (a set of Platform UUIDs). Setting deployed to true deploys the blueprint (and redeploys it if it falls out of date); false undeploys it. You can also use the software_update component to enforce a specific OS version by a date, the legacy_payloads attribute for traditional configuration profile payloads, or custom_declarations for arbitrary DDM declarations the provider doesn't yet have a dedicated component for.

Jamf Pro resources, same provider

Because the provider federates the Jamf Pro surface, you can manage Jamf Pro objects from the same project without adding another provider. They live under the jamfplatform_pro_* namespace and behave like any other resource:

resource "jamfplatform_pro_category" "productivity" {
  name     = "Productivity"
  priority = 9
}

resource "jamfplatform_pro_script" "install_rosetta" {
  name            = "Install Rosetta"
  category_id     = jamfplatform_pro_category.productivity.id
  priority        = "BEFORE"
  script_contents = file("${path.module}/support_files/scripts/install_rosetta.sh")
}

The same credentials and provider block cover both namespaces — there's nothing extra to configure.

Applying your configuration

The core workflow is the same regardless of which resources you define:

terraform init       # Initialize and download the provider
terraform plan       # Review what will be created, changed or destroyed
terraform apply      # Apply the changes (you'll be asked to confirm)

terraform destroy tears down what's in state. Each subsequent run consults your state file, works out what's changed and executes only the necessary actions.

Bringing an existing tenant under management

If you already have resources configured, you don't need to start from scratch. Every resource supports terraform import, and on Terraform 1.14+ the provider supports list resources — a way to query existing infrastructure directly from Terraform and generate configuration for it.

Create a query file (e.g. discover.tfquery.hcl):

list "jamfplatform_blueprints_blueprint" "software_update" {
  provider         = jamfplatform
  include_resource = true

  config {
    search = "software update"
  }
}

list "jamfplatform_device_group" "smart_computer_groups" {
  provider         = jamfplatform
  include_resource = true

  config {
    filter {
      selector = "deviceType"
      argument = "COMPUTER"
    }
    filter {
      join_with = "and"
      selector  = "groupType"
      argument  = "SMART"
    }
  }
}

Then run:

terraform query -generate-config-out=generated.tf

Terraform queries your tenant, returns the matching resources with their IDs, and generates both resource blocks and import blocks into generated.tf. List resources exist for the native Platform services and across the jamfplatform_pro_* surface, so the same approach scales well beyond blueprints and benchmarks.

Doing this by hand for an entire environment is a lot of work, so we built a tool that automates the whole loop — discovery, generation, cross-resource references, secret scanning and per-type file splitting. See Adopting Terraform for Jamf with jamformer.

Using alongside other Jamf providers

With the Jamf Pro surface federated in, this provider can manage most of a Jamf environment on its own. You can still combine it with others where it makes sense:

  • Jamf Protect Provider — for Jamf Protect resources (endpoint security plans, threat prevention, telemetry and more) via the Jamf Protect GraphQL API.
  • Deployment Theory Jamf Pro Provider — the community-maintained provider that started it all, which we hold in high regard. It can also authenticate through the Jamf Platform gateway (auth_provider = "platform"), sharing the same Jamf Account credentials as this provider.

Getting involved

The provider is open-source under the MPL license and published on both the HashiCorp and OpenTofu registries. It's built on HashiCorp's Terraform Plugin Framework (Protocol v6) and uses our open-source jamfplatform-go-sdk, which you can import into your own Go projects for scripting and automation.

We welcome contributions — bug reports, feature requests and pull requests are all appreciated. The provider will continue to grow in lockstep with the Jamf Platform APIs as more capabilities are added. For questions and discussion, join #terraform-provider-jamfplatform on the MacAdmins Slack.

References

Was this helpful?