jazzace.ca

Anthony’s Mac Labs Blog

📦 AutoPkg and Trust

Posted 2024 July 02

A couple of recent discussions in the #autopkg channel of the Mac Admins Slack got me thinking more deeply about trust in AutoPkg. Trust Verification was adopted in late 2016 with Version 1.0 of AutoPkg.[1] As of that version, all recipe overrides track the non-core processors and parent recipes that the override uses, such that changes to any of those will trigger a trust warning (and, if you have strict trust verification turned on, the recipe will not run until trust is re-established). The AutoPkg wiki describes this in more detail.

But that doesn’t cover everything that an Admin has to “trust” when running AutoPkg. So I thought it would be worthwhile to lay that out clearly for both the novice and more experienced user. I’m going to start with a chart that summarizes this and then describe what it all means.

The Trust Table

Item to Run Maintainer
Scrutiny
Trust
Verification
AutoPkg Code (including core processors) Strong No
Core recipes Strong Yes
Custom processors in Core recipes repo Strong Yes
AutoPkg Org recipe repos Minimal Yes
Custom processors in Org recipe repos Minimal Yes*
Recipe repos from other sources None Yes
Custom processors from other sources None Yes*
Recipes that you wrote (Your Own) Yes
Processors that you wrote (Your Own) Yes*
Any recipe run with strict trust verification set to False (or not set) See above No
Any processor run as a pre- or postprocessor or from an override See above No
* Any Python shared library loaded by these processors is not subject to Trust Verification.

In this table, “Maintainer Scrutiny” refers to how much oversight the item has seen from the AutoPkg Maintainers. “Trust Verification” refers to the automated process introduced in AutoPkg 1.0, referenced above.

It’s Trust Verification, Not Trust

Naming what AutoPkg does programatically as “trust verification” is no accident. Initial trust of a recipe/workflow is established by you, the Admin. It is assumed that, before you run any AutoPkg recipe in production, you will check that the recipe chain/AutoPkg command that you will run will do what you need it to do, without doing any damage (e.g., to your AutoPkg host). There are many ways to do this and you can use multiple ways (e.g., using the audit verb, reading the plist/YAML markup to determine what it does, testing the recipe in a disposable environment like a VM). Once you have confirmed that all is well, that is when trust verification becomes relevant, as that feature checks for changes from a trusted state.

The Maintainer Scrutiny column in the table above suggests how deliberate you might want to be in that initial testing based on how much scrutiny the recipes and/or processors have already gone through. The core processors and recipes have been scrutinized by one or more of the AutoPkg project maintainers and any future change made will receive the same, high level of scrutiny.

Once you start using the recipes and processors of others (as almost all of us do), the question of scrutiny becomes less clear. If the repo is in the AutoPkg org (i.e., github.org/autopkg/username-recipes), the repo may have been looked at when it was initially added to the org and the maintainers will occasionally examine recipes and processors for common errors (usually in an automated way), but that’s fairly minimal.

The phrase “Consider the source” comes to mind here. Some business enterprises have spent a lot of time on their repos and need them to work well for their customer base, so one might consider the recipes and processors from, for example, dataJAR-recipes (in the autopkg org) and KAPPA (in the kandji-inc org) as being fairly trustworthy, even though they may have had little or no scrutiny from the AutoPkg Maintainers. Likewise, running recipes written by you or someone you know personally is another kind of implicit trust that may affect what level of testing you do. Regardless, most of us load Git repos into AutoPkg, so have a plan on how you will test new additions to your AutoPkg setup.

One final caveat: Processors are Python code running as root. Even well-trusted processors can do damage when fed particular options (the core PathDeleter processor comes to mind). Establishing an initial trusted state is important.

What Trust Verification Does and Doesn’t Do

Once you establish that initial trusted state, there are three things that may need to change over time in order for your workflow to continue to work:

  1. AutoPkg code (including core processors),
  2. Recipes, and
  3. Custom/Shared Processors.

Changes to /Library/AutoPkg (where the AutoPkg code and core processors are stored) are not captured by Trust Verification.[2] For example, if you just updated to version 2.7.3 of AutoPkg, one of the core processors was updated and no updates were required to your overrides. Nevertheless, you are still responsible for testing the update just like you test any other software release. The Release Notes are a great help in determining what you should be inspecting, as they tend to be fairly thorough.

Recipe and custom/shared processor changes mostly fall under the Trust Verification model built in to AutoPkg. If you update a recipe or custom/shared processor, whether that is by updating the repo that has the changed item(s) in it, or by manually editing the item yourself (as you might for a recipe that you wrote and don’t have in a Git repo), trust verification will catch that change at runtime if you are running an override.[3] You still need to inspect the changes to verify that you still trust these recipes and/or processors, then update the trust in the override to reset the Trust Verification for that recipe.[4]

There are two things to take note of with the Trust Verification model:

Finally, AutoPkg allows you to run one or more processors before or after the recipe by using the --pre and or --post options for the run verb. If you use a processor that is not already in the recipe being run, it will not be checked by the trust verification mechanism. Nathaniel Strauss wrote in detail about this in a recent blog post and shared a technique (“vendoring”) that allows you to use processors in that context more securely. Along the same lines, recipe overrides generally don’t have a Process dictionary, but they absolutely can because they are a child recipe. Any processor steps you add to an override will not be scrutinized by the trust verification process.

One More Thing…

The final security consideration I want to highlight is a malicious actor modifying the code or recipes on the computer you are using to run AutoPkg. If malware (or even the practical joker in your office) changes anything prior to a run, only the changes covered by Trust Verification will be captured (and that’s only if the AutoPkg preferences are in tact). This is where people using ephemeral runners (e.g., CI/CD) have an advantage, as they are always loading from source. You should consider the threat of malware on your AutoPkg runner in the same way as you do any other Mac you support.

Trust But Verify

AutoPkg’s Trust Verification and the scrutiny of the AutoPkg maintainers takes care of much of what AutoPkg users need to ensure that AutoPkg is a secure tool to use. But the Admin running this versatile tool needs to be conscious of what they have to take care of themselves. Hopefully this article will help people understand where to put their focus in this regard.

Thanks to Graham Pugh for helping make this post more complete.


[1] If you want to more about why this was implemented, check out any of Elliot Jordan’s talks from that time on How (Not) To Do Bad Things With AutoPkg. [Return to main text]

[2] Anything you store alongside the AutoPkg code is not covered by Trust Verification. The old JSSImporter processors were “sideloaded” into the AutoPkg code because they pre-dated the syntax for calling shared processors. While I’m not aware of anyone adding things to the AutoPkg code this way any more, there is nothing magic in AutoPkg that recognizes stuff that is unexpected in /Library/AutoPkg. [Return to main text]

[3] If you are running a recipe directly rather than using an override, you will be notified that there is “No trust information present”, regardless of whether the recipe has changed or not. [Return to main text]

[4] If you want to do a run test of the updated recipe, you can use the --ignore-parent-trust-verification-errors option to turn off Trust Verification for just that run. I use that regularly when I am writing a new recipe or updating an old one. I have a shell alias that includes that option for me for ease of typing: aprt='autopkg run -vv --ignore-parent-trust-verification-errors'. (Level 2 verbosity is added because I’m always using this in a testing context.) [Return to main text]