Secrets - Jamf-Concepts/apiutil GitHub Wiki
Appendix: How to Keep a Secret
Traditional Methods for API Secrets in Scripts
Before we talk about the API Utility's approach to secret security, let's begin by acknowledging we have a problem.
Almost every one of us has written more or less these exact same lines of code:
#!/bin/sh
# Jamf Pro API Credentials
JAMF_URL="https://my_jamf_pro_url"
JAMF_USER="my-admin-username"
JAMF_PASSWORD="my-api-password"
...
Let's consider some of the security best practices we're violating here.
Principle of least access
JAMF_USER="my-admin-username"
Many of us just have a full admin user and re-use it for everything and we've gotten away with it. So far, at least. But a full admin login is the keys to the kingdom. Think of what could happen if those leaked out.
Most scripts only need to read a select set of data objects, and often only need write access to an even smaller set of data. Take the extra few minutes to setup an API Role and API Credential that's limited to just the access your script requires.
Don't leave secrets in plain sight
JAMF_USER="my-admin-username"
JAMF_PASSWORD="my-api-password"
It's easy for malware installed at even the user level to read that, or for us to forget to redact the secrets before sharing the script.
There are better methods. We could store the secrets in a hidden file in our user home directory and read them in from there, just like how things like curl can read their credentials from a .netrc file. Another approach with similar aims is to have the script fetch its secrets from environment variables. In both of these approaches, the secrets are still saved unencrypted at rest, but at least they're outside our project directory so it's a lot less likely we'll accidentally save it to GitHub or copy-paste them into a chat message along with some code. So in that respect, these strategies are better than putting the secrets right in our script, but they're still not super secure.
Avoid supplying secrets via command line parameters
To avoid having the secrets right in the script, or to allow a script to run against multiple servers, some scripts will do this instead:
#!/bin/sh
# Jamf Pro API Credentials
JAMF_USER="$1"
JAMF_PASSWORD="$2"
...
In the above example, we're feeding in the secrets from the command line. Similarly, we might sometimes try a curl from Terminal command line like this...
curl --user "my-username:my-password" "https://my.jamfcloud.com/api/v1/auth/token"
Both of these are not ideal practice because command line invocations that include secrets in their parameters can appear in the computer's process list and could find their way into system log files. Threat actors know all about it.
Interactive entry is OK
Still another practice is to prompt the user to enter the secrets interactively:
#!/bin/bash
# Prompt the user for Jamf Pro API credentials
read -p "Jamf Pro URL (e.g., https://my.jamfcloud.com): " JAMF_URL
read -p "Enter your Jamf Pro API username: " JAMF_USER
read -s -p "Enter your Jamf Pro API password: " JAMF_PASSWORD
...
That's a good way to do it, in theory, at least, but a password manager isn't going to supply that information like it would when we're logging into a web page and it's a bit of a hassle to have to type in our usernames and passwords all the time. So, human nature being what it is, we may be tempted to use short and simple passwords that are more vulnerable to brute-force attacks, or re-using one password to login to lots of different systems which can lead to privilege escalations.
Fetching Secrets from the macOS Keychain
A script can also retrieve secrets from a macOS keychain:
security find-generic-password -w -s "Service Name" -a "Account Name"
That approach has the significant advantage of achieving encryption at rest. But once a keychain is unlocked, its secrets are available to any process that's permitted access.
Securing Credentials with the API Utility
The API Utility tries to promote good practices by making it easier for us to do the right thing. We can create lots of target configurations, each with the minimum required authorization required by our applications. You only have to enter a target's secrets once when you're creating the target and thereafter you can call on them by name, making it much easier to use complex passwords and switch from using username and password logins to role-based access controls and client credential authentication. Also, the secrets stored in the keychain by the API utility can only be read by the API Utility, or by Keychain Access with an admin authentication. Other processes won't be able to access them.
Finally, there's an option to require a local user auth (e.g. press touch-ID) anytime something asks the API Utility to access the secret. So, for example, an admin in a higher-security environment might choose to turn that on for a credential that has write access to something sensitive.
API Secrets in other Languages
Lots of Mac admins write shell scripts to perform system administration tasks and automations that interact with APIs, but more advanced admins and integration developers are probably using languages like C#, PowerShell, Python, Ruby, or Swift. There's no reason why you couldn't call the API command line utility from any language that permits making a shell call to take advantage of all the same features the utility provides to shell scripts. But it's unlikely that you'll want to do that. Modern languages often have native SDKs for interacting with APIs and standard approaches for maintaining secret security.
Some Jamf API SDKs with native support for a variety of popular languages are listed in the Jamf Pro API Developer Resources page on our Developer site.
More information about Keychain security is available from Apple Developer: Storing Keys in the Keychain