Skip to content

Instantly share code, notes, and snippets.

@lepus2589
Last active February 6, 2025 11:09
Show Gist options
  • Select an option

  • Save lepus2589/5235d78172e529b2d076d2c0e9670b27 to your computer and use it in GitHub Desktop.

Select an option

Save lepus2589/5235d78172e529b2d076d2c0e9670b27 to your computer and use it in GitHub Desktop.
Mac OS X `ssh-agent` with `ssh-askpass`

The Problem

Recently, I switched to KeePassXC for managing my passwords and SSH keys. And of course, I wanted to use the ssh-agent integration with ssh-askpass offered by KeepassXC.

I'm using a Mac for work. I installed ssh-askpass from MacPorts and thought I was done. Alas, Apple recently prevented system agents (like ssh-agent) from accepting additional environment variables (in this case: SSH_ASKPASS), probably for security reasons. This means, that the login item provided by the ssh-askpass package does not work any more (see theseal/ssh-askpass#54).

Possible Solutions

Two workarounds are being offered in this issue. The first one (theseal/ssh-askpass#54 (comment)) involves installing the XQuartz package, just to get the DISPLAY variable set and then relies implicitly on a default location, where the system ssh-agent looks for ssh-askpass in the absence of an SSH_ASKPASS variable. This, to me, seems unwieldy and kind of fragile. From a helpful comment, I learned, that this is the intended way it should work (and does indeed on Linux and BSD).

The second approach (theseal/ssh-askpass#54 (comment); theseal/ssh-askpass#54 (comment)) works by masking the system ssh-agent .plist with a user copy and also masking the system ssh-agent socket with a symbolic link to the socket from the user copy. This approach appeals to me because it's simple, non-invasive to the system (I was reminded in a comment, that replacing the system agent in itself is a potential security issue, which is correct, of course) and trivially reversible. All .plist and script files are located in the users ~/Library/LaunchAgents directory, so no system directories need to be touched.

My Implementation

The Overlay ssh-agent Service

The com.openssh.ssh-agent-overlay.plist goes to ~/Library/LaunchAgents. It's based on a copy of the system ssh_agent .plist, as suggested in this comment: theseal/ssh-askpass#54 (comment). The executed program has been replaced with the com.openssh.ssh-agent-overlay.sh script, which I put into ~/Library/LaunchAgents/bin (remember to make it executable with chmod +x). Please adjust the path in the .plist file accoding to your system.

This service sets up a parallel infrastructure to the system ssh-agent. It requests another socket from the system and starts our ssh-agent listening to this socket... but only if there is an interaction with this socket! Which nobody knows about!

The Link Service

The second component to make it work is the symbolic link. Copy the com.openssh.ssh-agent-overlay-link.plist to ~/Library/LaunchAgents and the com.openssh.ssh-agent-overlay-link.sh to ~/Library/LaunchAgents/bin (or wherever you like). Again, adjust the program path in the .plist file and make the script file executable.

The .plist and script files work in tandem to run at user login and wait for the other service to provide the alternative socket (when available, this will inject the SSH_AUTH_SOCK_OVERLAY variable into the whole launchd user domain). As soon as the alternative socket is available, the symbolic link masking the system socket and pointing to the alternative socket is created. This works, because the system ssh-agent, though being provided by the system, is started per user in the user domain. So the user has write access to the socket. Also, the script kills any previous ssh-agent processes (which could happen, if another login item had already accessed the system ssh-agent's socket).

Now, any interaction by any program with the system provided socket in the SSH_AUTH_SOCK variable is forwarded to our alternative socket. The first interaction with it will execute the com.openssh.ssh-agent-overlay.sh script starting our instance of ssh-agent.

And this user provided login agent DOES pick up any other environment variables injected into the launchd user domain (before it's started, of course). That means, the ssh-askpass login item does work with it, too.

Don't forget to reboot after installing all the files and adjusting the paths in the .plist files!

Verifying

If everything went well, after a reboot, you should see this:

$ launchctl list | grep ssh
-	0	com.openssh.ssh-agent-overlay-link
-	0	com.openssh.ssh-agent
-	0	com.openssh.ssh-agent-overlay

$ launchctl print gui/$UID/com.openssh.ssh-agent-overlay
gui/503/com.openssh.ssh-agent-overlay = {
  ...
  inherited environment = {
    SSH_AUTH_SOCK_OVERLAY => /private/tmp/com.apple.launchd.<random chars>/Listeners
    SSH_AUTH_SOCK => /private/tmp/com.apple.launchd.<different random chars>/Listeners
  }
  ...
}

$ readlink -f $SSH_AUTH_SOCK
/private/tmp/com.apple.launchd.<random chars>/Listeners    // == $SSH_AUTH_SOCK_OVERLAY

$ ssh-add -l
The agent has no identities.

$ launchctl list | grep ssh
-		0	com.openssh.ssh-agent-overlay-link
-		0	com.openssh.ssh-agent
<random PID>	0	com.openssh.ssh-agent-overlay

Uninstallation

Uninstalling this workaround, if it becomes unnecessary or breaks something else (that I'm not aware of), is trivial. The first and easiest thing to do is deactivating the two login items via the system settings. If you want to purge them from your system, delete the two .plist files from ~/Library/LaunchAgents and the two script files from wherever you placed them.

Also, don't forget to reboot to revert to the system ssh_agent.

<?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">
<!-- Copyright (c) 2024 Apple Inc. All rights reserved. -->
<dict>
<!-- This first part has been copied from the system ssh-agent .plist and suffixed with '-overlay' -->
<key>Label</key>
<string>com.openssh.ssh-agent-overlay</string>
<key>Sockets</key>
<dict>
<key>Listeners</key>
<dict>
<key>SecureSocketWithKey</key>
<string>SSH_AUTH_SOCK_OVERLAY</string>
</dict>
</dict>
<key>EnableTransactions</key>
<true/>
<key>Program</key>
<string>/Users/<user name>/Library/LaunchAgents/bin/com.openssh.ssh-agent-overlay.sh</string>
</dict>
</plist>
#!/bin/sh
# MIT License
# Copyright (c) 2024 Tim Kaune
# This wrapper script will be executed on overlay ssh-agent socket access.
# Overwrite local ssh-agent socket variable with the overlay ssh-agent socket.
SSH_AUTH_SOCK=$SSH_AUTH_SOCK_OVERLAY
# Start our instance of ssh-agent and replace the executing shell
# to match the PID accessing the overlay ssh-agent socket.
exec /usr/bin/ssh-agent -l
MIT License
Copyright (c) 2024 Tim Kaune
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@lepus2589

Copy link
Copy Markdown
Author

Thanks for the clarification! I'll update the gist's description accordingly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment