In my day job I work with a lot of Linux servers, all only accessible via a bastion host. To make things slightly more complicated it is a password-based login with a TOTP1 two-factor step. I use SSH ControlMaster but still need to log in frequently.
The 2FA setup makes automation difficult, or at least so I assumed, but the other day I finally managed to solve it.
I am very happy and paid-up user of BitWarden (my vault contains over 350 passwords), and my usage in this scenario would typically involve switching over to my browser, opening the extension then searching for and copying the password, back to my terminal to paste it in, back to the browser to search for and copy the TOTP code, and finally back to the terminal again to finalise login. It beats remembering and typing, but it’s still frustrating.
BitWarden has a number of clients, including for the command-line, but because the vast majority of my usage is browser-based I’d never bothered downloading it. It did seem like the perfect candidate to automate my password-entry papercuts though.
Installation of the CLI is
straightfoward, either via
npm or several package-management
Usage is also fairly simple:
bw unlockwill unlock your vault (and
bw statusreports several statistics included locked status).
- Unlocking outputs a line to paste into your shell that sets an environment variable for the current session.2
bw get password <item-id>will print the plain-text password for an item. You can of course search for items to find this id.
Output is in JSON, if you need to script anything in conjunction with jq for example.
As a bonus, you can enable shell completion via the tool itself:
eval "$(bw completion --shell zsh); compdef _bw bw;"
When I was noodling this idea around my first thought was some variant
However: expect is designed around automating the entire session, whereas all I want is to be dropped into a shell with less typing. So, that wasn’t going to work.
Second, successful, attempt
SSH goes to some lengths to avoid this type of automation, but it is feasible. There is a tool that allows several methods of password entry (file, CLI, environment variable) and passes it to SSH: https://sourceforge.net/projects/sshpass/
Disclaimer: Possibly like you, I saw “sourceforge” and also assumed it must be abandoned. However it does appear that the maintainer, while busy, is interested in considering modern needs for the tool (just not adding patches that solve one specific case but not the general requirements).
As of now it doesn’t support TOTP codes though, but there are numerous forks with varying approaches; I settled on https://github.com/dora38/sshpass
This variant supports TOTP codes by specifying an executable file
which generates a code. Ours just needs to be a script containing
bw get totp <id>.
I didn’t want to have a file containing my password, even temporarily,
so my first instinct was to use
bw to pipe the password in; eg
bw get password $passwordid | sshpass....
This doesn’t work because ssh then realises that stdin is redirected and doesn’t open a pseudo-tty.
So, my ultimate solution was to use an environment variable, just for
that invocation (
-e to indicate it should look for a variable, and
you probably also want to configure the password and TOTP prompts):
SSHPASS=$(bw get password $passwordid) sshpass -e -c generate-totp ssh firstname.lastname@example.org
Right now this is what I do, and it’s only a control-r away (even
easier to search for since I’ve started using
fzf). I did look at turning it
into a command for the purposes of this post, but there were a few
- an alias is problematic because of the amount of quoting, and not wanting the get-password sub-shell to be prematurely evaluated.
- A shell function would be ideal, but… doesn’t give me an interactive prompt at the end.
I’m sure it’s solvable, but for a quick post I didn’t invest enough effort — sorry!