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.
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.