Ler blog em Português

Using 1password-cli to avoid hardcoded secrets in your terminal profile

Read in 3 minutes

One of the things that had always bothered me when creating terminal profiles is hardcoding secrets like in export GITHUB_TOKEN=<SECRET>. This is even more annoying when you have more than one computer and need to synchronize secrets between them.

I finally decided to handle this issue with 1password-cli, a command-line utility that can interact with 1Password.

For the sake of this article, I’ll skip the 1password-cli setup and I’m assuming the command op is available on your terminal.

$ which op
/Users/fnando/local/bin/op

The first step is signing in by running the command op signin. Once you’re done you can list your vaults by running the command op vault ls.

$ op vault ls
ID                            NAME
zyxlrxyu53kax7qqrxz554fgbq    Private
oblww37jzanxpl4wruzjz43i71    dev

In my case, I’ll use the vault dev to store my development credentials. You can either create a new item by using the command-line or 1Password’s UI. I’m using a server type:

$ op item create --title main --category server --vault dev
ID:          kbfa7n6hcziy6ck7ys63pzta3a
Title:       main
Vault:       dev (oulww37jzinxpl4wruzjz43i44)
Created:     now
Updated:     now
Favorite:    false
Version:     0
Category:    SERVER
Fields:
  Admin Console:

  Hosting Provider:

I recommend opening 1Password and removing the default sections; unfortunately, I couldn’t find a way of removing sections by using the command-line. You can also remove the default fields, as we won’t use it (password, username and url).

Now you can add secrets to that item by using the command op item edit. Let’s add two secrets by using the command-line. Or you can do the same using 1Password’s user interface.

$ op item edit --vault dev main 'dev.SOME_API_KEY[password]=SOME_KEY' 'dev.ANOTHER_API_KEY[password]=SOME_OTHER_KEY'
ID:          kbfa7n6hcziy6ck7ys63pzta3a
Title:       main
Vault:       dev (oulww37jzinxpl4wruzjz43i44)
Created:     40 seconds ago
Updated:     now by Nando Vieira
Favorite:    false
Version:     2
Category:    SERVER
Fields:
  dev:
    SOME_API_KEY:       SOME_KEY
    ANOTHER_API_KEY:    SOME_OTHER_KEY

The secrets above will be added to a section called dev. Here’s a screenshot showing the secrets in 1Password’s app.

1Password screenshot showing the items we've created

Once you’ve added all secrets to 1Password, it’s time to retrieve the secrets. You can do it so by running the command op item get.

$ op item get --vault dev main --format json
{
  "id": "kbfa7n6hcziy6ck7ys63pzta3a",
  "title": "main",
  "version": 3,
  "vault": {
    "id": "oulww37jzinxpl4wruzjz43i44",
    "name": "dev"
  },
  "category": "SERVER",
  "last_edited_by": "XCFD5SQIJNCZZOF2PBC7XED6QE",
  "created_at": "2022-12-28T08:44:55Z",
  "updated_at": "2022-12-28T00:45:35.033579-08:00",
  "sections": [
    {
      "id": "Section_6r6xfx7vkbniby2fogqg7tvmee",
      "label": "dev"
    }
  ],
  "fields": [
    {
      "id": "notesPlain",
      "type": "STRING",
      "purpose": "NOTES",
      "label": "notesPlain",
      "reference": "op://dev/main/notesPlain"
    },
    {
      "id": "xzuhdgcumcnz5peyvjckq2z4gi",
      "section": {
        "id": "Section_6r6xfx7vkbniby2fogqg7tvmee",
        "label": "dev"
      },
      "type": "CONCEALED",
      "label": "SOME_API_KEY",
      "value": "SOME_KEY",
      "reference": "op://dev/main/dev/SOME_API_KEY"
    },
    {
      "id": "4mg4ejw7q3pnsyworyiflofemq",
      "section": {
        "id": "Section_6r6xfx7vkbniby2fogqg7tvmee",
        "label": "dev"
      },
      "type": "CONCEALED",
      "label": "ANOTHER_API_KEY",
      "value": "SOME_OTHER_KEY",
      "reference": "op://dev/main/dev/ANOTHER_API_KEY"
    }
  ]
}

Notice that 1Password will always return the field notesPlain, as it’s built-in and can’t even be removed.

The next step is using this list and convert it into a format that your terminal will understand. I’m going to use jq for this task.

$ op item get main --vault dev --format json | jq -r '.fields | map(select(has("value"))) | map("export " + .label + "=\"" + .value + "\"") | join("\n")'
export SOME_API_KEY="SOME_KEY"
export ANOTHER_API_KEY="SOME_OTHER_KEY"

You can either use eval with the above output, or output to a file that your terminal will load. I’m going with the second option, but first let’s clean up the house and create an executable script. I’m going to call it op-env.

#!/usr/bin/env bash

set -e

op item get main --vault dev --format json | jq -r '.fields | map(select(has("value"))) | map("export " + .label + "=\"" + .value + "\"") | join("\n")'

Make it executable by running chmod +x op-env. You should see the exports being printed to the screen if you run op-env.

$ op-env
export SOME_API_KEY="SOME_KEY"
export ANOTHER_API_KEY="SOME_OTHER_KEY"

Now you can add the following lines to your profile. If you’re using ZSH, ~/.zshrc will do trick. On Bash you can use ~/.bashrc:

if [ ! -f ~/.op-env ]; then
  op run -- true
  op-env > ~/.op-env
  chmod 600 ~/.op-env
fi

source ~/.op-env

The code above will create the file ~/.op-env unless one already exists, which means your secrets will be cached until this file is removed or you refresh it by running op-env > ~/.op-env. This is required because 1Password will request authorization every first call to op in a terminal session; that is, if you open a new tab, you’ll need to re-authorize op. By caching the results, you only need to do this every once in a while.

Wrapping up

That’s it! Using the 1password-cli allowed me to drop hardcoded secrets, making the secrets sharing way easier.

If you’re using 1password-cli, make sure you also take a look at the .env file support, which is also a neat way of sharing secrets between team members.

How do you handle this problem? Do you use something else? Share your favorite solution in the comments below.