maciej łebkowski
Maciej Łebkowski

Remote sudo password using 1password (or any other password manager)

in Professional

Having a local, secure and passwordless sudo is easy, especially with apples built in biometrics support. But what if most of your sudo prompts are on a remote machine you ssh into? Well, we can set this up too.

Prerequisites

  • We have a set of target hosts we SSH into and escalate our privileges using sudo, which requires a password
  • You have some kind of secure password manager on your local machine. I’ll be using 1password on a mac, but the solution is more general than that.

The problem

Our initial setup looks like this:

  • We SSH into a remote machine
  • Call sudo whatever
  • We need to type a password

And our target architecture is the following:

  • We SSH into a remote machine
  • Call sudo --askpass whatever
  • Behind the scenes, your password manager is queried for password, and you grant access using your regular method, and it gets delivered to sudo

I don’t want to spoil too much, but our solution will include defining SUDO_ASKPASS, an askpass script, ssh tunnel between two separate sockets, a script to talk to the password manager and a sprinkle of some configuration on top. Let’s get to it.

Creating a script to print the password on stdout

This is the simples part. Just use whatever scripting or compiled language to fetch a specific password from your vault and dump it to standard output. I use 1password, so I’ll make use of 1Password CLI utility called op:

#!/usr/bin/env bash

main() {
  declare account="$1" uuid="$2" field="${3:-password}"
  /opt/homebrew/bin/op read --account "$account" "op://Employee/$uuid/$field"
}

main "$@"

Since the script will not be executed from my login shell, I passed the full path to the op binary (as found by command -v op), because my $PATH settings will not be present in that context.

I have my password saved in a work account, so my default vault is named Employee. I got the item uuid from 1Password app. You can learn more about secret reference syntax to adjust to your context.

Let’s confirm that it works. After I saved this to ~/bin/op-sudo.sh and adding necessary permisions chmod a+x op-sudo.sh:

Everything works fine. The password manager asks me for confirmation first, and the password is printed on stdout.

Setting up a LaunchAgent (inetd) to listen on a socket

This next part will create a local socket, that will map to the script we just created. On linux machines there was this thing called inetd which opened a port for us, listened for connections, and mapped the script’s stdio to the socket. Today, I guess systemd can do that, and here’s a person describing how to set that up.

I am using a macOs machine instead, so I will create a LaunchAgent instead. LaunchAgents use a very similar concept, but they are configured via XML instead. Fill in some blanks in the following config and save it in ~/Library/LaunchAgents with a plist extension. I used the name pl.narzekasz.op-sudo.plist.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Disabled</key>
    <false/>
    <key>Label</key>
    <string>pl.narzekasz.op-sudo.35s524did7b74qggqeoqbetpee</string>
    <key>ProgramArguments</key>
    <array>
        <string>/Users/puck/bin/op-sudo.sh</string>
        <string>WonderNetwork</string>
        <string>35s524did7b74qggqeoqbetpee</string>
    </array>
    <key>inetdCompatibility</key>
    <dict>
        <key>Wait</key>
        <false/>
    </dict>
    <key>InitGroups</key>
    <true/>
    <key>Sockets</key>
    <dict>
        <key>Listeners</key>
        <dict>
            <key>SockPathMode</key>
            <integer>384</integer>
            <key>SockPathName</key>
            <string>/Users/puck/.op-sudo/wondernetwork.sock</string>
            <key>SockType</key>
            <string>stream</string>
        </dict>
    </dict>
</dict>
</plist>

There are some key placeholders there you need to pay attention to:

  • I used a pl.narzekasz.pl.op-sudo.{id} Label, because I anticipate I might have more such configurations to serve different passwords. This does not matter that much.
  • The ProgramArguments is an array representing the command invokation, so it contains the full path to the script as well as its arguments (1password account and item uuid). There’s nothing stopping you to call op directly here instead of having an intermediate script:

    <string>/opt/homebrew/bin/op</string>
    <string>--account</string>
    <string>WonderNetwork</string>
    <string>read</string>
    <string>op://Employee/{place-your-id-here}/{field}</string>
    
  • And at the bottom there’s SocketPathName defining a path to the unix domain socket that will be used to listen for incomming connections. Make sure to create the directory first, or the agent won’t start: mkdir /Users/puck/.op-sudo

Finally, we need to launch the agent:

launchctl load ~/Library/LaunchAgents/pl.narzekasz.op-sudo.plist

And I will use socat to confirm that I can get the password using the specified socket:

Creating a tunnel to the socket

Now, we need to somehow transfer that socket to the remote host. Fortunately, we can simply use ssh remote forwarding for that. I will quickly show how to do that by opening a TCP port of target machine, which is less secure, because every user on that system will be able to access it. That’s not a huge issue, since the script asks for confirmation each time, but it’s better if we can avoid that.

Using a TCP port

The way we connect would be:

ssh -R 7825:/Users/puck/.op-sudo/wondernetwork.sock target-host

We’re good to go.

Using a Unix Domain socket

Let’s do that properly and permanently instead. Add the remote forwarding instruction to your /config for target host or group. And here’s the part where you can set up multiple launch agents, serving different passwords, listening on different sockets, forwarded to different remote machines.

Host narzekasz.pl
  RemoteForward /home/puck/.ssh/op-sudo.sock /Users/puck/.op-sudo/wondernetwork.com

We can confirm that this still works:

There is one catch. The remote socket won’t be cleaned up by the sshd after you disconnect, so a dead socket file will be left in the filesystem. And next time you try to connect: sshd will bail, because the target path already exists. This behaviour can be overriden using the StreamLocalBindUnlink option, unfortunately, this needs to be set in the sshd_config of the target host, which might be out of reach in many situations.

Final piece of the puzzle: sudo-askpass

Finally, we need to tie this together with sudo on the remote host. We can use the SUDO_ASKPASS environment variable for this. First, let’s save a script that reads from our forwarded unix domain socket. You can use either netcat (nc) or socat for that, whatever is more convenient for you:

#!/usr/bin/env bash

has-command() {
  command -v "$1" >/dev/null 2>&1
}

main() {
  SOCKET="$(realpath ~/.ssh/op-sudo.sock)"
  if has-command nc; then
    echo | nc -U "$SOCKET"
  elif has-command socat; then
    socat stdio "UNIX-CONNECT:$SOCKET"
  else
    echo "Type the passwords your own self" >&2
    return 1
  fi
}

main "$@"

Save it to wherever, say ~/bin/sudo-askpass (remember to chmod a+x ~/bin/sudo-askpass) and configure it in your , or similar:

export SUDO_ASKPASS=~/bin/sudo-askpass

Enjoy! Remember to run your sudo with --askpass option!

Please leave likes and comments below to let me know if you enjoyed this instruction or have anything to improve

Was this interesting?

About the author

My name is Maciej Łebkowski. I’m a full stack software engineer with 25 years of experience, currently part of the WonderProxy team.

https://wondernetwork.com/about

Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. This means that you may use it for commercial purposes, adapt upon it, but you need to release it under the same license. In any case you must give credit to the original author of the work (Maciej Łebkowski), including a URI to the work.