There are several other questions that discuss accessing the keychain from LaunchAgents.
One of the key ones is here where joensson mentions that you need to set a <SessionCreate>
in your app plist.
I have done that, and now my application plist looks like this:
<?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>Label</key>
<string>com.ionic.python.ionic-fs-watcher.startup</string>
<key>ProgramArguments</key>
<array>
<string>/Applications/IonicFSWatcher.app/Contents/MacOS/ionic-fs-watcher</string>
<string>--debug</string>
<string>/Users/timothy/ionicprotected</string>
<string>--scan</string>
</array>
<key>UserName</key>
<string>timothy</string>
<key>SessionCreate</key>
<true />
</dict>
</plist>
The app is a python application, which was created with pyinstaller, packaged using pkgbuild, and installed via the command line.
The application runs fine when run from the command line. If the application is run for the first time, the user gets a prompt to allow access to the keychain, the app continues from there.
When it is launched as a LaunchAgent
, however, I get a return code of 9216 when attempting to access the keychain. Here is the exact command sequence I have been using to test:
# Window 1
sudo launchctl unload -w ~/Library/LaunchAgents/com.ionic.python.ionic-fs-watcher.startup.plist
sudo launchctl load -w ~/Library/LaunchAgents/com.ionic.python.ionic-fs-watcher.startup.plist
sudo launchctl debug system/com.ionic.python.ionic-fs-watcher.startup --stdout --stderr
# Window 2
sudo launchctl kickstart -k -p system/com.ionic.python.ionic-fs-watcher.startup
In the python script, I have been debugging by running a few subcommands and capturing output.
# OK
status, output = commands.getstatusoutput("security list-keychains")
logger.error("Keychain list :%s (retcode = %s)" % (
output, status
))
# OK
status, output = commands.getstatusoutput("security find-generic-password -a 'Ionic Security' ")
logger.error("Keychain list item :%s (retcode = %s)" % (
output, status
))
# Fails, with error code: 9216
# Prompts immediately when running from command line
status, output = commands.getstatusoutput("security find-generic-password -a 'Ionic Security' -g")
logger.error("Keychain access :%s (retcode = %s)" % (
output, status
))
The output of that section of code looks like this:
# Correctly shows both keychains
ERROR:root:Keychain list : "/Users/timothy/Library/Keychains/login.keychain"
"/Library/Keychains/System.keychain" (retcode = 0)
# Correctly lists information about keychain item
ERROR:root:Keychain list item :keychain: "/Users/timothy/Library/Keychains/login.keychain"
<redacted>
# Fail
ERROR:root:Keychain access : (retcode = 9216)
These same commands work fine from the command line, with the last one (-g
) resulting in a prompt to allow access to the keychain.
I have also tried opening up the entry for com.ionicsecurity.client.sdk
in the KeyChain Access
app, and set the "Allow all applications to access this item" radio button. After doing that, grabbing the value from the cli no longer resulted in a prompt but the app returns the same error code.
I have searched for information about error code 9216 with no results. Running the code through the security errors
utility just gives
$ security error 9216
Error: 0x00002400 9216 unknown error 9216=2400
Any help on how I can get the application access to the keychain when running as a LaunchAgent would be much appreciated!
The problem was the domain that I was using to launch the LaunchAgent. I was launching into the root system domain instead of the launching into the gui domain of the user I was setting up the LaunchAgent for. Because of this
Here is the mapping from the commands I was using before (not working) to what I'm using now (working). The commands assume the user for which we are installing the LaunchAgent is also the current user.
Activating the agent
Deactivating the agent
Setting permissions for the LaunchAgent definition (plist file)
LaunchAgent plist file contents
The
UserName
andSessionCreate
blocks are not needed.