Managing Jamf Configuration with Terraform: An Introduction

Prev

Legacy “Click-Ops” approaches to dealing with modern SaaS apps and Enterprise services are fast becoming legacy approaches for many organizations. Teams concerned with future proofing and codifying their setups have started to look to GitOps friendly approaches to managing these systems.

For good reason, too.

Introduction to Infrastructure as Code (“IaC”) and Continuous Integration / Continuous Deployment (CI/CD)

An increasing number of organizations prefer to implement changes via code or configuration definition files rather than relying on administrators clicking around in a console, otherwise commonly known as ClickOps. In a CI/CD (or "GitOps") approach, a desired state is defined, typically in a YAML file or another well-structured format, and the file is committed to a Git branch. A pull request is submitted to initiate a testing, review, and approval workflow, followed by a sequence of merges to a branch where an automated action implements the change in Jamf Pro via API. These techniques can also be used to move a change through test and user acceptance stages before anything happens in production where it will impact an entire fleet.

Defining configuration as code allows well defined code review, approval, and pull request workflows in much the same way as professional software development. This means your environment becomes testable, repeatable, and scalable while also providing a path to roll back to a previous version if a change causes unexpected issues.

You can rapidly iterate and rapidly revert without having to comb through a user interface alongside documentation to understand where the failure occurred.

Our team spoke about this whole approach at JNUC 2025 in Denver. In this write up, we intend to go a bit further and hopefully give you a clear runway to start digging into this yourself.

First, why did we choose Terraform?

Terraform is a very powerful tool in the Infrastructure as Code world - it’s nimble and can be crafted to map to any API that you’d like it to talk to. When we started looking into this whole approach, we connected with a Deployment Theory who had already built out a Terraform Provider for Jamf Pro. Our team began working with them to understand the application of it and have since contributed a good bit to that project. Additionally, we have even built our own providers for Apple Business and Apple School Manager as well as building a new provider for the Jamf Platform API.

We have documented these pieces and will continue to update this in the future on our Developer site here: https://developer.jamf.com/jamf-pro/docs/jamf-pro-api-developer-resources#terraform-providers

Before we go much further, please do not test this in a production instance. Terraform is a powerful tool so we recommend utilizing a test instance. You can acquire a beta Jamf Pro instance from Jamf Account to use for this purpose if you do not have a sandbox or test instance.

Now, let’s get you started using Terraform to build out some resources in Jamf Pro.

Note: To keep things simple, we’ll be using basic auth but you can switch to using OAuth in the future.

Installing Terraform

We’ll be using Homebrew to install Terraform for simplicity reasons. If you do not have Homebrew installed, please follow the steps here.

Open Terminal and go through the following steps:

First, we’ll access the HashiCorp official directory.

brew tap hashicorp/tap

Next, we’ll install Terraform.

brew install hashicorp/tap/terraform

Finally, we’ll validate that everything worked properly. Running this command should show you the version of Terraform you have installed. If it does not, retry the steps above.

terraform -version

Of course, HashiCorp does have their installation steps documented for reference here.

Installing Visual Studio Code

You can really use any text editor or Integrated Development Environment (IDE) for writing Terraform. However, we have found that Visual Studio Code supports Terraform very well with extensions for syntax highlighting readily available. Since VS Code is also a free tool, it makes it ideal for us to recommend for this testing.

You can download VS Code here and walk through installation on your system.

Once you’ve installed VS Code, you can click on the Extensions tab on the left side (or type Shift + Command + X on your keyboard) and search for “HashiCorp Terraform.” Go ahead and install this extension to get full Terraform syntax highlighting.

Starting a Terraform Project

A Terraform Project file will contain many specific items that are required to get up and running and start building out your project. Most of the relevant files will end with the file extension .tf or something similar like .tfvars.

There are many components that make up the functionality here but we’ll cover those at a later time.

Getting started here can feel a bit daunting - there is a lot to get up and running with. So, we wanted to make this a bit easier.

First, let’s open Terminal and create a folder so we can store our Terraform project. We’ll create this folder at your user level for easy access.

mkdir jamf-terraform

Then, we’ll change directory in Terminal over to the newly created folder

cd ~/jamf-terraform

Now, we’ll clone our template branch from our main Terraform module repo.  

git clone -b template https://github.com/Jamf-Concepts/terraform-jamf-platform

After you’ve done this, you’ll have a fully functional template Terraform project ready to go for Jamf Pro and Jamf Security Cloud.

Adding a Variable File

Something you don’t want to end up in your Github repo - your login credentials or client secrets. Thankfully, Terraform has a path for this.

Open up your text editor of choice and copy in the following text:

## Jamf Pro Account Details
jamfpro_auth_method   = "basic" ## oauth2 or basic
jamfpro_instance_url  = "https://<tenant-name>.jamfcloud.com" 
jamfpro_username      = ""
jamfpro_password      = ""
jamfpro_client_id     = ""
jamfpro_client_secret = ""

## Jamf Protect Account Details
jamfprotect_url             = “https://<tenant-name>.protect.jamfcloud.com”
jamfprotect_clientid        = ""
jamfprotect_client_password = ""


## (Jamf Pro) General Settings Knobs ##
include_categories = false

You’ll name this file terraform.tfvars and save it to the top level of your newly cloned template repo.

Now, you can start to fill in the information in your newly created terraform.tfvars file. I have set the authentication method to basic to simplify things for you but know that you can change to oauth2 in the future. So, for now, you’ll just enter your local username and password into the jamfpro_username and jamfpro_password fields respectively. You can also enter your Jamf Protect information - you’ll just need to create an API Client in your Protect tenant.

We’ve also included a knob for the one included module with this template repo. This is effectively a simple boolean variable that lets us declare whether we’ll be applying any particular module. You can choose to use this kind of approach with the additional modules you built in the future or simply just strip out all boolean requirements and just let everything run each time.

Terraform State File

Since we haven’t actually committed any real resources to our instance just yet - now is the perfect time to talk about what the state file is in Terraform.

Every time you add, change, or destroy resources through Terraform, those updates are saved in a file called terraform.tfstate and then backed up to a file called terraform.tfstate.backup.

This is how Terraform remembers what it has done in your instance and it’s critical for forward functionality. Each time you run Terraform against an instance, this state file is consulted before anything is done. Each state file needs to be associated to its own instance so it’s important to keep them separated to maintain functionality and avoid crossing the streams.

Running Terraform

Now, you’re in a good place - you have Terraform installed, a template repo, and your credentials defined in a variables file.

Now, you’re ready to run our template module against your test or beta Jamf Pro instance. To do so, you’ll open Terminal and run the following commands.

terraform init -upgrade

This will initialize Terraform and upgrade any required providers to the latest versions for anyone who’s used this before on their machines but it will also do the initial install for the required Terraform providers if you’re entirely new to the topic.

terraform fmt -recursive

This will recursively format any Terraform code in your local cloned branch to make sure it will all look correct and function properly.

terraform plan

This will look at your test Jamf Pro instance, your Terraform state file, and your Terraform module code for any changes or new things to apply or remove from your instance and return a full plan or rundown on what will happen when you run the module fully.

terraform apply

This will first run the plan and report back what will be added, changed, or destroyed. It will then ask you to confirm this action. The field will only accept 2 inputs: yes or no.

terraform apply -parallelism=1

This will force all resources being created to take their turns and go one at a time. Since many SaaS apps have systems in place to detect potential attacks, sometimes this is the best way to approach running Terraform.

terraform destroy -parallelism=1

This will destroy any created resources that are referenced in your state file in Terraform.

Each subsequent run of any of the plan, apply, or destroy commands will consult your state file, assess what needs to be added, changed, or destroyed and then execute the proper action.

Running This Particular Module

When you run the template module that we have included, it will create 7 categories in your Jamf Pro named for each Apollo mission from 11-17 as a simple test.

Once these are added, you can easily destroy them using the above commands.

The larger goal here is to show you the structure of Terraform so you can start building relevant modules for your environment which brings me to the next section of this write up.

The Anatomy of Terraform

The core of the functionality here is the hierarchy of .tf files. The root level main.tf calls to the child module main.tf which references the .tfvars files and variables.tf files. Here is a full breakdown of the thread and order of communication:

When you run terraform apply:

state file is consulted.

Root main.tf is assessed for child modules that are being requested.

Root variables.tf is scanned for relevant variables for the child modules being applied.

terraform.tfvars is scanned for all relevant variables for running each child module.

Child module main.tf files are then called and executed referencing their own local variables.tf files for relevant local child module variables.

Each resource is then created and references are saved to terraform.tfstate.

Terraform will report back success or failure along with relevant codes and response messaging for you to use in troubleshooting issues if necessary.

Things to Keep in Mind

When you start building your own modules, you can copy the template module we included and change it to suit your needs.

Each child module must have its own main.tf and variables.tf files.

Each child module needs to be represented in the root level main.tf.

Carefully read through the module in the template repo and look for every reference point so you fully understand how each piece works together to achieve the goal.

You can use boolean operations (also known as knobs) in your terraform.tfvars file to call child modules or simply leave them as always being called whenever you run terraform apply.

You can also have your module calls be dependent on a variable being populated or you can even set up simple inclusion or exclusion statements to determine when modules run based on your own criteria.

Terraform is a very powerful tool with many built-in functions that you can use. Some of these will be necessary as you build specific functions and some you’ll never use but they’re all very well documented over at https://developer.hashicorp.com/terraform

References

Deployment Theory Jamf Pro Provider

Jamf Platform Provider

Jamf Security Provider

Jamf AutoUpdate Provider

Jamf Created Example and Reference Modules