macoskeychainlaunchdlaunchdagentionicsecurity

Error code 9216 when attempting to access keychain password in LaunchAgent


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!


Solution

  • 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

    # Before
    sudo launchctl load -w ~/Library/LaunchAgents/com.ionic.python.ionic-fs-watcher.startup.plist
    
    # Now
    launchctl enable user/`id -u`/com.ionic.python.ionic-fs-watcher.startup
    launchctl bootstrap gui/`id -u` ~/Library/LaunchAgents/com.ionic.python.ionic-fs-watcher.startup.plist
    

    Deactivating the agent

    # Before
    sudo launchctl unload -w ~/Library/LaunchAgents/com.ionic.python.ionic-fs-watcher.startup.plist
    
    # Now
    launchctl bootout gui/`id -u`/com.ionic.python.ionic-fs-watcher.startup
    launchctl disable user/`id -u`/com.ionic.python.ionic-fs-watcher.startup
    

    Setting permissions for the LaunchAgent definition (plist file)

    # Before
    chown root:wheel ~/Library/LaunchAgents/com.ionic.python.ionic-fs-watcher.startup.plist
    chmod 644 ~/Library/LaunchAgents/com.ionic.python.ionic-fs-watcher.startup.plist
    
    # Now
    chown "`id -un`":"`id -gn`" ~/Library/LaunchAgents/com.ionic.python.ionic-fs-watcher.startup.plist
    chmod 644 ~/Library/LaunchAgents/com.ionic.python.ionic-fs-watcher.startup.plist
    

    LaunchAgent plist file contents

    The UserName and SessionCreate blocks are not needed.

    <?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>/Users/timothy/ionicprotected</string>
            <string>--scan</string>
        </array>
      </dict>