1password

How to securely store secrets in 1Password CLI and load them into your ZSH shell when needed

Zack wrote a post titled “How to securely store secrets in BitWarden CLI and load them into your ZSH shell when needed.” This is a companion piece, with my own twist.
How to securely store secrets in 1Password CLI and load them into your ZSH shell when needed
Pete Emerson
Published April 11, 2023

Zack wrote a post titled “How to securely store secrets in BitWarden CLI and load them into your ZSH shell when needed.” This is a companion piece, with my own twist.

In this blog post, I’ll show you how to quickly and easily use the command line to retrieve secrets from 1Password and set environment variables so that your credentials are available when needed but never stored unencrypted on your local computer.

Demo

As the demo shows, there are no GitHub environment variables set initially. We then use env-op (a shortened form of “environment — 1Password”) to retrieve the github demo credentials from 1password. It prompts us for our fingerprint or password, and then the credentials are loaded into the environment variables named in the username field of the credential.

Since we use a convention of storing the name of the environment variables in the username field in 1password, env-op can load arbitrary environment variables, and the naming of those variables is determined via the credential itself.

Let’s walk through the process.

Step 1. Install the 1Password CLI

1Password has excellent docs for their CLI. You can find them, along with comprehensive installation instructions, here.

Step 2. Add a new API Credential to your 1Password Vault

In this case, I’ve named the API Credential “github demo”. Note that the username contains two entries separated by a space: GITHUB_TOKEN and GITHUB_OAUTH_TOKEN. The credential stored will be loaded into both environment variables.

Step 3. Verify that your token is available from the command line

The API Credential should now be stored and available from the op command line. We can verify this:

# Check to see that the credentials we stored are available to the op binary
$ op item get 'github demo' --fields username
GITHUB_TOKEN GITHUB_AUTH_TOKEN

# You might skip this so that your secret is not exposed to STDOUT
$ op item get 'github demo' --fields credential
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

Step 4. Write your shell function

Open ~/.zsh_autoload_functions/env-op for editing and write the following to the file:

env-op() {
local search="$@"
keys_string=$(op item get --fields label=username  "$search")
if [[ $? != 0 ]] ; then
return
fi
values_string=$(op item get --fields label=credential "$search")
read -A keys <<< "$keys_string"
read -A values <<< "$values_string"
# If we store "AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY" in the username field
# and "secret1 secret2" in the password field, we'll map them accordingly.
# If there are fewer secrets than usernames (space separated), then we'll
# use the last value for the rest of the secrets mapped to usernames.
# For example: "GITHUB_TOKEN GITHUB_AUTH_TOKEN" with a password of "foo" will
# set both environment variables to "foo".
for ((i = 1 ; i <= $#keys ; i++)) ; do
if [[ "$values[$i]" != "" ]] ; then
last_value=$values[$i]
fi
export $keys[$i]=$last_value
>&2 echo "Loaded $keys[$i]"
done
}

env-op "$@"

Note that you can store multiple environment variables (and values) in the same 1password credential! If there are fewer values than keys, the last value will be mapped to the rest of the keys. This could be useful for storing AWS credentials (the access key ID and the secret access key) in one 1Password entry (although we do recommend using aws-vault for such credentials).

Step 5. Instruct ZSH to autoload your new function

Open your ~/.zshrc file and add the following lines to it:

# Autoload zsh shell functions defined in the function path
fpath=( ~/.zsh_autoload_functions "${fpath[@]}" )
autoload -Uz env-op

The first line tells ZSH to look for functions defined in the ~/.zsh_autoload_functions directory.

The second line states that ZSH should treat the env-op function as a function and autoload it using the ZSH shell style.

Write these changes to your ~/.zshrc file, then save it and run zsh in a new shell to ensure your latest changes are loaded.

You can now invoke env-op "name of 1password api credential" and enter your main vault password (or use your fingerprint if that is configured) to have your credentials easily exported for use.