Scripting with sudo on Mac
I was writing up an “advanced scripting” example for the Bunch documentation and realized this tip is useful enough generally to be worth cribbing for a post.
The gist is this: when you need to script a tool that requires administrator privileges, you want to make the process as automated as possible without creating glaring security problems (like including a password in plain text).
An example of why you’d want to do this would be the tmutil
command line utility, which allows you to perform Time Machine tasks from the command line. But the enable
and disable
commands require sudo
to work, so pausing Time Machine from a script isn’t as easy as just running tmutil disable
. You’d either have do some terribly insecure things to /etc/sudoers
, or store your Mac’s most valuable password in plain text in a script. Neither option is good.
Fortunately, macOS has tools built in to make this work. We’ll use a combination of macOS’s Keychain Access
and the security
command to make running superuser tasks both convenient and secure.
Side note: 1Password has a command line tool that can do something very similar to this, but it requires signing in on the command line. The method below allows you to have a script run in the background, only popping up a password dialog when needed.
The Keychain
The macOS Keychain is where all of your logins and certificates are stored for everything on your Mac. It’s encrypted and secure, storing everything behind a single password. You have to unlock it (enter your password) at intervals, but once it’s unlocked, every app that needs to access an account can pull the credentials it needs from it quietly. You use it every day, even if you’re not directly aware of it.
The Keychain is the center of this tip. We’re going to use it to store the password for our root
user, and then use command line tools to access that password, never having to store it in plain text. If your Keychain locks, you’ll be asked to enter a password, but whenever it’s already unlocked (which happens any time any app needs credentials), we’ll be able to retrieve it silently.
Adding a Password Entry to Your Keychain
The first step is to create a Keychain entry for the password you want to use. In our case, this will be your system password.
Open Keychain Access in /Applications/Utilities. Unlock your login
keychain if needed, then click the “Create new” button in the toolbar. Give the item a unique name, any account name you want, and then enter the password and click “Add.”
Note that you can right click on the login toolchain in the left sidebar and use Change Settings… to tell it how long to remain unlocked after entering your password. If your Mac is private and you want it to remain unlocked for as long as you’re logged in, disable “Lock after…” and enable “Lock when sleeping.” Otherwise a generous timeout will provide enough convenience to make it worthwhile.
Keychain Access From the Command Line
Now this password entry can be accessed using the command line tool security
, which we can use in a script. If the keychain is unlocked, the password will be retrieved without interaction. If it’s locked, you’ll get a dialog asking for your keychain password when the script runs.
In our script, we’ll call security
and give it the name assigned to the keychain item (-l
), and the account name (-a
). The -w
flag tells it to return only the password (otherwise there’s a lot of data it spits out).
I’m using the label “root password” with user “root” for this example, just to be overly obvious about it.
security find-generic-password -l "root password" -a root -w
# │ │ └ return just the password
# │ └ "account" name for the entry
# └ "label" for the keychain entry
To incorporate this into a shell script, we’ll just use tr
to trim the newline off it and save it to a variable, and then pipe it to sudo
. Using sudo -S
tells sudo
to read the password from STDIN (the result of the pipe).
#!/bin/bash
PASS=$(security find-generic-password -l "root password" -a root -w|tr -d '\n')
echo "$PASS" | sudo -S tmutil disable
# └ read password from STDIN
# Gracefully stop current backup
# Doesn't require root
tmutil stopbackup
The first time
security
is used from a script, you’ll get a prompt to allow access. Be sure to click “Always Allow” to avoid getting the same prompt every time.
For more info on how to use this in Bunch, check out the Using Sudo page in the docs.