The process of creating and publishing a new open-source macOS authorization plugin has taught me a few things about handling the list of login mechanisms in the authorization database. It also helped me realize that existing methods of maintaining the database often rely upon Python — a dependency I preferred to avoid.
In this post, I’ll go over the basics of managing the login mechanism list using shell scripts, which may be of interest to organizations who deploy Escrow Buddy, Crypt, or XCreds. I’ll also share a script that includes flexible shell functions for those who manage authorization plugins but don’t want or need to deploy a Python runtime.
Authorization plugins
macOS provides numerous ways for developers and IT administrators to run processes in the background. The most common ones — including LaunchDaemons and LaunchAgents — are widely understood, used, and abused in the Mac admin and security community. But there are situations where a daemon or agent isn’t the ideal tool for the job, and one of these situations is automation and decision-making around user login.
Enter macOS authorization plugins. Apple’s documentation describes their use:
A typical use for authorization plug-ins is to implement policies that are not included in the standard authorization configuration.
In the Mac admin world, such policies could include federating local accounts to cloud identity providers, providing additional account setup automation during the provisioning process, or handling situations that require user credentials like keychain syncing and FileVault key generation.
Authorization database
Authorization plugins can have multiple “mechanisms” that can serve specific purposes or run with specific privileges. In order to register these mechanisms with the macOS authorization services API, they need to be listed in the system.login.console
section of the authorization database, within the mechanisms
array.
The command security authorizationdb read system.login.console
will display the current mechanisms in a plist. (macOS stores the state of these mechanisms in multiple places, but using the security
command and other supported APIs is the best way to ensure any automation you create is stable and long-lived.)
As of macOS Sonoma, the command above produces the following output:
% security authorizationdb read system.login.console
<?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>class</key>
<string>evaluate-mechanisms</string>
<key>comment</key>
<string>Login mechanism based rule. Not for general use, yet.</string>
<key>created</key>
<real>470991139.20000899</real>
<key>mechanisms</key>
<array>
<string>builtin:prelogin</string>
<string>builtin:policy-banner</string>
<string>loginwindow:login</string>
<string>builtin:login-begin</string>
<string>builtin:reset-password,privileged</string>
<string>loginwindow:FDESupport,privileged</string>
<string>builtin:forward-login,privileged</string>
<string>builtin:auto-login,privileged</string>
<string>builtin:authenticate,privileged</string>
<string>PKINITMechanism:auth,privileged</string>
<string>builtin:login-success</string>
<string>loginwindow:success</string>
<string>HomeDirMechanism:login,privileged</string>
<string>HomeDirMechanism:status</string>
<string>MCXMechanism:login</string>
<string>CryptoTokenKit:login</string>
<string>loginwindow:done</string>
</array>
<key>modified</key>
<real>717879783.86331403</real>
<key>shared</key>
<true/>
<key>tries</key>
<integer>10000</integer>
<key>version</key>
<integer>11</integer>
</dict>
</plist>
YES (0)
The output has two parts: a plist representing the current state of the authorization database (sent to the stdout pipe), and a status message indicating whether the query was successful (to stderr). Here’s an example of an unsuccessful query generating no stdout and an error code in stderr:
% security authorizationdb read bogus-right-name
NO (-60005)
Resets can happen
During some macOS upgrades and updates, the list of login mechanisms in the authorization database gets reset to its default state. This is understandable, as the introduction of new macOS features sometimes requires new entries in the database, and Apple probably aims to avoid the risk of incompatible third-party mechanisms breaking the login process.
Default authorization database
The fresh authorization database is synthesized from /System/Library/Security/authorization.plist. Referring to this file on a given version of macOS can help you understand which mechanisms are standard and in which order they're found.
If you deploy an authorization plugin to your managed Macs, the potential for periodic reset puts the onus on you and your team to be aware of when the plugins you deploy might have been removed from the authorization database and — after testing and ensuring compatibility with new macOS versions — re-adding your plugin mechanism to the authorization database to restore functionality.
Auditing desired state
In order to check whether your custom mechanism is present in the authorization database, a simple grep
can provide the necessary logic:
% security authorizationdb read system.login.console | grep "<string>YourPlugin:Mechanism</string>"
<string>YourPlugin:Mechanism</string>
YES (0)
If you’re planning on including this logic in a script, you may not want any output. To suppress output, we can add -q
to the grep
command, and redirect stderr to /dev/null.
% security authorizationdb read system.login.console 2>/dev/null | grep -q "<string>YourPlugin:Mechanism</string>"
This will produce a predictable exit code: zero if the desired mechanism is present, non-zero otherwise. An example of using this in a script-based collection mechanism (like a Jamf extension attribute) would be:
However, sometimes knowing whether the mechanism is in the database isn’t sufficient. Because the order of the mechanisms in the array represent the order they’re executed during login, you may want to capture where in that array your mechanism is.
This example extension attribute returns the index (beginning at zero) of the specified mechanism, or -1
if the mechanism isn’t listed:
Modifying the mechanism list
If your desired entry is missing from the list of login mechanisms in the authorization database, and you’ve tested to ensure compatibility with the current macOS version, you can provide an updated plist as input to the security
command to add your entry.
Making a backup
Before making any changes, it’s not a bad idea to save a backup of the plist in case restoration is required. We can do that by saving the plist to disk and making a copy:
security authorizationdb read system.login.console 2>/dev/null > /tmp/system.login.console.plist
cp /tmp/system.login.console.plist /tmp/system.login.console.plist.backup
Then we can edit the system.login.console.plist file as needed.
Validating input
Because errors in the authorization database can potentially prevent login from succeeding, extra diligence in catching errors is warranted. Login mechanism identifiers follow a predictable format that we can validate:
We can use grep
to ensure any provided mechanism names match a regular expression:
MECHANISM="YourPlugin:Mechanism"
if ! grep -qE '^[^,:]+:[^,:]+(,privileged)?$' <<< "$MECHANISM"; then
echo "ERROR: Specified right is not formatted correctly: $MECHANISM"
exit 1
fi
Relative positioning
When adding mechanisms to the array, we’ll need to specify an index; the new mechanism will be inserted into the array at that index, incrementing subsequent items’ indices by 1. Therefore, it may be useful to reference an existing item in the array that we can insert relative to. In the example below, we’re calculating the index of the built-in loginwindow:done
mechanism.
INDEX=$(/usr/libexec/PlistBuddy -c "Print :mechanisms:" /tmp/system.login.console.plist 2>/dev/null | grep -n "loginwindow:done" | awk -F ":" '{print $1}')
INDEX=$((INDEX-2))
Writing changes
Finally, we can use PlistBuddy’s Add
verb to modify the plist on disk, which we can feed as input to the security authorizationdb write system.login.console
command to apply the changes to the authorization database:
/usr/libexec/PlistBuddy -c "Add :mechanisms:$INDEX string 'MyPlugin:Action'" /tmp/system.login.console.plist
security authorizationdb write system.login.console < /tmp/system.login.console.plist
Removing mechanisms
If we need to remove a mechanism from the list, we can use a similar process to do so. First we’ll find its index. Then we’ll remove it using PlistBuddy’s Delete
verb. Then we’ll write the plist back to the database.
INDEX=$(/usr/libexec/PlistBuddy -c "Print :mechanisms:" /tmp/system.login.console.plist 2>/dev/null | grep -n "MyPlugin:Action" | awk -F ":" '{print $1}')
INDEX=$((INDEX-2))
/usr/libexec/PlistBuddy -c "Delete :mechanisms:$INDEX" /tmp/system.login.console.plist
security authorizationdb write system.login.console 2>/dev/null < /tmp/system.login.console.plist
Tying it all together
In order to simplify the process of managing system.login.console
mechanism entries for IT administrators, I’ve produced a shell script with various functions for dealing with the authorization database. This script incorporates the concepts covered above, and adds informative output, error handling, and sensible use of variables to make customization and implementation easier.
You can find this script on GitHub and embedded below. To use it, uncomment and customize the examples at the bottom of the script to suit your needs.
Further reading
If you’re interested in learning more about managing the system.login.console
right of the macOS authorization database, check out the articles and code examples below:
- Csaba Fitzl: Beyond the good ol’ LaunchAgents - 28 - Authorization Plugins
- DssW: system.login.console - Overview of the macOS Authorization Right (includes default mechanisms for macOS 10.6 through 10.14)
- Charles Edge: authorizationdb Defaults in macOS 10.14
- Erik Berglund: Scripts/random/modifyAuthorizationDB/modifyAuthorizationDB
- Jamf: Modifying the loginwindow Application
- JumpCloud: support/scripts/macos/remove_mac_agent.sh (clever one-line removal method using
sed
) - Airbnb: macadmins/puppet-authpluginmech: A method of managing mechanisms for authorization plugins
- Airbnb: puppet-crypt/manifests/config.pp
And if you’re interested in the other rights managed by the authorization database, see:
- Rich Trouton: Managing the Authorization Database in OS X Mavericks
- Armin Briegel: Demystifying
root
on macOS, Part 4 —The Authorization Database - Nathaniel Strauss: Authorization Rights Management for Standard User Access
- Apple: Introduction to Authorization Services Programming Guide (archived, but helpful context)