How about using 1Password with Ansible?

2021-11-23 - Progress - Tony Finch

i have been looking at how to use the 1Password op command-line tool with Ansible. It works fairly nicely.

You need to install the 1Password command-line tool.

You need a recent enough Ansible with the community.general collection installed, so that it includes the onepassword lookup plugin

To try out an example, create an op.yml file containing:

---
- hosts: localhost
  tasks:
  - name: test 1password
    debug:
      msg: <{{ lookup("onepassword",
                      "Mythic Beasts",
                      field="username") }}>

You might need to choose an item other than Mythic Beasts if you don't have a login with them.

Initialize op and start a login session by typing:

eval $(op signin)

Then see if Ansible works:

ansible-playbook op.yml

Amongst the Ansible verbiage, I get the output:

ok: [localhost] => {
    "msg": "<hostmaster@cam.ac.uk>"
}

Some more detailed notes follow...

aims

I want it to be easy to keep secrets encrypted when they are not in use. Things like ssh private keys, static API credentials, etc. "Not in use" means when not installed on the system that needs them.

In particular, secrets should normally be encrypted on any systems on which we run Ansible, and decrypted only when they need to be deployed.

And it should be easy enough that everyone on the team is able to use it.

what about regpg?

I wrote regpg to tackle this problem in a way I consider to be safe. It is modestly successful: people other than me use it, in more places than just Cambridge University Information Services.

But it was not the right tool for the Network Systems team in which I work. It isn't possible for a simple wrapper like regpg to fix gpg's usability issues: in particular, it's horrible if you don't have a unix desktop, and it's horrible over ssh.

1password

Since I wrote regpg we have got 1Password set up for the team. I have used 1Password for my personal webby login things for years, and I'm happy to use it at work too.

There are a couple of ways to use 1Password for ops automation...

secrets automation and 1password connect

First I looked at the relatively new support for "secrets automation" with 1Password. It is based around a 1Password Connect server, which we would install on site. This can provide an application with short-term access to credentials on demand via a REST API. (Sounds similar to other cloudy credential servers such as Hashicorp Vault or AWS IAM.)

However, the 1Password Connect server needs credentials to get access to our vaults, and our applications that use 1Password Connect need API access tokens. And we need some way to deploy these secrets safely. So we're back to square 1.

1password command line tool

The op command has basically the same functionality as 1Password's GUIs. It has a similar login model, in that you type in your passphrase to unlock the vault, and it automatically re-locks after an inactivity timeout. (This is also similar to the way regpg relies on the gpg agent to cache credentials so that an Ansible run can deploy lots of secrets with only one password prompt.)

So op is clearly the way to go, though there are a few niggles:

  • The op configuration file contains details of the vaults it has been told about, including your 1Password account secret key in cleartext. So the configuration file is sensitive and should be kept safe. (It would be better if op stored the account secret key encrypted using the user's password.)

  • op signin uses an environment variable to store the session key, which is not ideal because it is easy to accidentally leak the contents of environment variables. It isn't obvious that a collection of complicated Ansible playbooks can be trusted to handle environment variables carefully.

  • It sometimes reauires passing secrets on the command line, which exposes them to all users on the system. For instance, the documented way to find out whether a session has timed out is with a command line like:

    $ op signin --session $OP_SESSION_example example
    

I have reported these issues to the 1Password developers.

Ansible and op

Ansible's community.general collection includes some handy wrappers around the op command, in particular the onepassword lookup plugin. (I am not so keen on the others because the documentation suggests to me that they do potentially unsafe things with Ansible variables.)

One of the problems I had with regpg was bad behaviour that occurred when an Ansible playbook was started when the gpg agent wasn't ready; the fix was to add a task to the start of the Ansible playbook which polls the gpg agent in a more controlled manner.

I think a similar preflight task might be helpful for op:

  • check if there is an existing op session; if not, prompt for a passphrase to start a session

  • set up a wrapper command for op that gets the session key from a more sensible place than the environment

To refresh a session safely, and work around the safety issue with op signin mentioned above, we can test the session using a benign command such as op list vaults or op get account, and run op signin if that fails.

The wrapper script can be as simple as:

#!/bin/sh
OP_SESSION_example=SQUEAMISHOSSIFRAGE /usr/local/bin/op "$@"

Assuming there is somewhere sensible and writable on $PATH...