Sharing Secrets With the 1Password CLI

Sometimes you need secrets in your local dev environment, be it a token for an api or a client secret for an OAuth2 provider. But of course you cannot commit those secrets to the Git repository. So whenever a new secret is added or a new member joins the team, you will need to have a way to share those secrets.

Often times it is badly documented which secrets you need and how to obtain them, so that new team members need to ask around, and the old team members need to remember where to find them, and they will likely share them in a direct Slack message. There must be a better way.

If your team already uses 1Password, you can make use of the 1Password CLI. Let’s see how you can use it in a shell script that you can safely commit to our repo.

Set up

Install the 1password cli as described in the Getting Started guide. As described there, you need to sign in once after the installation with:

op signin example.1password.com someone@example.com

After that, you can just sign in with:

# Fish
eval (op signin example)

# Bash
eval $(op signin example)

You will need to do this before running the example script below. You will stay signed in for 30 minutes.

If you have multiple 1Password accounts, you can specify the account to use with the --account flag.

Basics

You can get the item with the name SomeItem from the vault TeamVault with:

op get item SomeItem --vault TeamVault

The output will be raw json, but you can use jq to bring it into a more usable form. To pretty print the output, just run:

op get item SomeItem --vault TeamVault | jq

In general, items can be referenced by their display name (overview.title) or by their uuid (uuid).

The fields (username, password etc.) can be found under the details key. Unfortunately, the output of the 1Password CLI is not very consistent. For login items, the details are an object like this:

{
  "details": {
    "fields": [
      {
        "name": "password",
        "type": "P",
        "value": "hk0ikb7ki0ae7k07bk"
      },
      {
        "designation": "username",
        "name": "username",
        "type": "T",
        "value": "mail@example.com"
      }
    ]
  }
}

A password item has details like this:

{
  "details": {
    "password": "somepassword",
    "sections": [
      {
        "name": "linked items",
        "title": "Related Items"
      },
      {
        "fields": [
          {
            "k": "string",
            "n": "HWGKWG86PGWPHKGWP67PKW",
            "t": "iaeui",
            "v": "iaeuiaoo"
          }
        ],
        "name": "Section_FKFGZKFG87ZFGZKZFGHKZ87",
        "title": "section"
      }
    ]
  }
}

And a server item has multiple sections under details, while the keys in the field objects are just single characters:

{
  "details": {
    "sections": [
      {
        "fields": [
          {
            "inputTraits": {
              "autocapitalization": "none",
              "autocorrection": "no"
            },
            "k": "string",
            "n": "username",
            "t": "client_id",
            "v": "ifugae7b0u98i7ape078b"
          },
          {
            "k": "concealed",
            "n": "password",
            "t": "client_secret",
            "v": "up908mußpffugh7pb-89bu7iae"
          }
        ],
        "name": "",
        "title": ""
      }
    ]
  }
}

This can make parsing the data a bit tricky. For our use case, I think it is a good idea to stick to the item type server. In the last example, you can extract the client_secret like this:

eval op get item MyItem | jq ".details.sections[0].fields[] | select(.t==\"client_secret\").v" -r

The script

Now that we know how to retrieve items and secrets via the command line, we can put the logic into a script.

This is the fish version (you could put the 1p_server_field function in a separate file in ~/.config/fish/functions to make it available on your system):

#!/usr/bin/env fish

set vault TeamVault

function 1p_server_field -d "retrieves a field from a 1password server item" -a item_name section_index field_name
  eval op get item $item_name --vault $vault | jq ".details.sections[$section_index].fields[] | select(.t==\"$field_name\").v" -r
end

set -x SOME_CLIENT_ID (1p_server_field some_item_name 0 client_id)
set -x SOME_CLIENT_SECRET (1p_server_field some_item_name 0 client_secret)

And here the same as a bash script:

#!/bin/bash -eux
vault=TeamVault
1p_server_field () {
  echo "$(eval op get item $1 --vault $vault | jq ".details.sections[$2].fields[] | select(.t==\"$3\").v" -r)"
}
export SOME_CLIENT_ID=$(1p_server_field some_item_name 0 client_id)
export SOME_CLIENT_SECRET=$(1p_server_field some_item_name 0 client_secret)

Save this as env.sh and run . env.sh.

You can safely commit a script like this to your repository. Nobody has to ask anymore where to find the credentials, and after rolling a secret, all you have to do is to run this script again.

Don’t stop believing.

originally published on Medium