age-op
This commit is contained in:
parent
72844a78f1
commit
3120446a7e
4 changed files with 228 additions and 0 deletions
221
bin/age-op
Normal file
221
bin/age-op
Normal file
|
@ -0,0 +1,221 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Age encryption using secrets in 1password vault
|
||||
# Date: 2024-06-04
|
||||
# Version: 0.3
|
||||
# License: MIT or APACHE-2.0
|
||||
# Author: @stevelr
|
||||
# Author: @anthonycicc
|
||||
#
|
||||
# Run `age-op -h` for help and examples
|
||||
#
|
||||
# For information about age, see https://github.com/FiloSottile/age
|
||||
# Tested on mac and linux. Tested with age and rage (rust-rage)
|
||||
|
||||
# check dependencies
|
||||
# Change to 'rage' if you prefer
|
||||
AGE=${AGE:-$(which age)}
|
||||
OP=${OP:-op}
|
||||
|
||||
|
||||
# Select private folder for temporary secrets. can be overridden with `-t` flag
|
||||
# The defaults for linux and macos are readable by owner only
|
||||
# Returns: temp folder path with no trailing '/'
|
||||
private_tmp() {
|
||||
if [ -d /run/user/$(id -u) ]; then
|
||||
echo /run/user/$(id -u) # linux. folder owned by user with mode 700
|
||||
elif [ -d "$TMPDIR" ]; then
|
||||
echo "$(echo $TMPDIR | sed 's./$..')" # macos. owned by user with mode 700. remove trailing slash
|
||||
else
|
||||
echo "$PWD"
|
||||
fi
|
||||
}
|
||||
tmppath=$(private_tmp)
|
||||
|
||||
|
||||
# Display help message and quit
|
||||
# param: ERROR_MESSAGE
|
||||
# If ERROR_MESSAGE is not empty, exits 1, otherwise exits 0
|
||||
_help() {
|
||||
[ -n "$1" ] && echo "Error: $1\n"
|
||||
local ds="\$"
|
||||
local prog=$(basename $0)
|
||||
cat <<_HELP
|
||||
|
||||
age encryption with secret keys in 1password vault
|
||||
|
||||
Examples:
|
||||
|
||||
Encrypt file:
|
||||
$prog -e -k KEY_PATH [ -o OUTPUT ] [ -t TMPDIR ] [ FILE ]
|
||||
encrypt a single input file FILE.
|
||||
If FILE is '-' or not specified, stdin is encrypted
|
||||
If OUTPUT is '-' or not specified, the output is sent to stdout
|
||||
|
||||
To encrypt one or more files or folders to a tar file, use
|
||||
tar czf - FILE_OR_DIR FILE_OR_DIR ... | $prog -e -k KEY_PATH -o foo.tar.gz.age
|
||||
|
||||
Decrypt file:
|
||||
$prog -d -k KEY_PATH [ -o OUTPUT ] [-t TMPDIR ] [ FILE ]
|
||||
decrypt a file, or stdin
|
||||
If FILE is '-' or not specified, stdin is decrypted
|
||||
If OUTPUT is '-' or not specified, the output is sent to stdout
|
||||
|
||||
To decrypt a tar file,
|
||||
$prog -d -k KEY_PATH foo.tar.gz.age | tar xzf -
|
||||
|
||||
Generate an age ed25519 key and store it in the 1password vault. The type of the new item will be "Password"
|
||||
$prog -n -k KEY_PATH
|
||||
|
||||
KEY_PATH should have one of the following formats:
|
||||
- 'op://vault/title', 'op://vault/title/field', or 'op://vault/title/section/field'
|
||||
In the first case, the field name defaults to 'password'
|
||||
|
||||
TMPDIR is the temporary folder where key will be briefly written and quickly removed
|
||||
Default is '$tmppath'
|
||||
|
||||
1Password configuration:
|
||||
For the 1Password cli ('op') to authenticate with a vault, you can do one of the following:
|
||||
- For use with a service account, set the environment variable OP_SERVICE_ACCOUNT_TOKEN
|
||||
- For use with a 1Password Connect Server, set OP_CONNECT_HOST and OP_CONNECT_TOKEN
|
||||
- sign into a local app with "eval ${ds}(op signin)"
|
||||
|
||||
Dependencies: Installation instructions and documentation:
|
||||
age: https://age-encryption.org
|
||||
op (1Password cli): https://developer.1password.com/docs/cli/get-started
|
||||
|
||||
_HELP
|
||||
[ -n "$1" ] && exit 1 || exit 0
|
||||
}
|
||||
|
||||
# Store key in unique temp file, with access limited to current user
|
||||
# params: TMPDIR KEY
|
||||
# returns: path to temp file
|
||||
store_secret() {
|
||||
secret=$(mktemp "$1/age-secret.XXXXXX")
|
||||
chmod 600 "$secret"
|
||||
cat >"$secret" <<_EKEY
|
||||
$2
|
||||
_EKEY
|
||||
echo "$secret"
|
||||
}
|
||||
|
||||
# Create a new key
|
||||
# params: KEYPATH
|
||||
new_key() {
|
||||
local keypath="$1"
|
||||
local key field out pw title vault field
|
||||
|
||||
##
|
||||
## Create new key
|
||||
##
|
||||
vault=$(echo $keypath | sed -E 's|op://([^/]+)\/([^/]+)\/(.*)|\1|')
|
||||
title=$(echo $keypath | sed -E 's|op://([^/]+)\/([^/]+)\/(.*)|\2|')
|
||||
field=$(echo $keypath | sed -E 's|op://([^/]+)\/([^/]+)\/(.*)|\3|')
|
||||
|
||||
# check if the key path exists so we don't overwrite it.
|
||||
# The successs case (key is unique) generates an error, so temporarily disable '+e'
|
||||
set +e
|
||||
key=$($OP item get "$title" "--vault=$vault" 2>/dev/null)
|
||||
[ $? -eq 0 ] && _help "Key vault:$vault title:$title already exists - will not overwrite"
|
||||
set -e
|
||||
pw="$($AGE-keygen)"
|
||||
out=$($OP item create --category=password --title="$title" --vault="$vault" "$field=$pw")
|
||||
echo "Created vault:$vault title:$title"
|
||||
}
|
||||
|
||||
cmd=""
|
||||
input=""
|
||||
output=""
|
||||
keypath=""
|
||||
stdin=0
|
||||
_err=""
|
||||
# putting this in a variable makes it work with zsh
|
||||
help_regex="^\-h|^--help|^help$"
|
||||
|
||||
[ ! $($AGE --help 2>&1 | grep Usage) ] && _help "Missing 'age' or 'rage' dependency. Please see installation url below."
|
||||
# 1password cli
|
||||
[ ! $($OP --version) ] && _help "Missing 'op' dependency. Please see installation url below."
|
||||
[[ $1 =~ $help_regex ]] && _help
|
||||
|
||||
while getopts ':hnedo:k:t:' OPTION; do
|
||||
case $OPTION in
|
||||
h) _help
|
||||
;;
|
||||
n) [ -n "$cmd" ] && _help "Only one of -e, -d, or -n may be used"
|
||||
cmd="new"
|
||||
;;
|
||||
e) [ -n "$cmd" ] && _help "Only one of -e, -d, or -n may be used"
|
||||
cmd="encrypt"
|
||||
;;
|
||||
d) [ -n "$cmd" ] && _help "Only one of -e, -d, or -n may be used"
|
||||
cmd="decrypt"
|
||||
;;
|
||||
o) output=$OPTARG
|
||||
;;
|
||||
k) keypath=$OPTARG
|
||||
if [[ -v AGE_KEYPATH ]]; then
|
||||
keypath=$AGE_KEYPATH
|
||||
fi
|
||||
if [[ ! $keypath =~ ^op://[^/]+/[^/]+/.+$ ]]; then
|
||||
# if path has only two segments (vault & title), append field "password"
|
||||
# since the 'new' function creates items of type Password
|
||||
if [[ $keypath =~ ^op://[^/]+/[^/]+$ ]]; then
|
||||
keypath="$keypath/password"
|
||||
else
|
||||
_help "Invalid key path '$keypath'"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
t) tmppath=$OPTARG
|
||||
[ ! -d "$tmppath" ] && _help "Invalid tmp folder: '$tmppath' does not exist"
|
||||
;;
|
||||
?) _help "" ;;
|
||||
esac
|
||||
done
|
||||
shift "$(($OPTIND -1))"
|
||||
|
||||
[ -z "$cmd" ] && _help "One of -e, -d, or -n must be used"
|
||||
[ -z "$keypath" ] && _help "keypath is required. Should be of the form op://vault/title[/field]"
|
||||
|
||||
if [ "$cmd" = "new" ]; then
|
||||
new_key $keypath
|
||||
else
|
||||
|
||||
##
|
||||
## Encrypt or Decrypt
|
||||
##
|
||||
if [ -z "$1" ] || [ "$1" = "-" ]; then
|
||||
stdin=1
|
||||
else
|
||||
input="$1"
|
||||
[ ! -r "$input" ] && _help "Missing or unreadable input file '$input'"
|
||||
# don't re-encrypt file ending in .age
|
||||
[ "$cmd" = "encrypt" ] && [[ $input =~ \.age$ ]] && _help "Input file may not end in '.age'"
|
||||
fi
|
||||
if [ -z "$output" ] || [ "$output" = "-" ]; then
|
||||
output=/dev/stdout
|
||||
fi
|
||||
|
||||
key=$($OP read "$keypath")
|
||||
if [ $? -ne 0 ] || [ -z "$key" ]; then
|
||||
_help "Invalid keypath '$keypath'"
|
||||
fi
|
||||
secret=$(store_secret "$tmppath" "$key")
|
||||
|
||||
## try
|
||||
{(
|
||||
set +e # don't quit on error here - we want to make sure secret is deleted
|
||||
if [ $stdin -eq 1 ]; then
|
||||
$AGE --${cmd} -i "$secret" >"$output"
|
||||
else
|
||||
$AGE --${cmd} -i "$secret" <"$input" >"$output"
|
||||
fi
|
||||
)}
|
||||
## catch
|
||||
{
|
||||
rm -f "$secret"
|
||||
}
|
||||
fi
|
||||
|
||||
unset _err cmd input key keypath output secret stdin tmppath
|
|
@ -45,6 +45,10 @@ in {
|
|||
source = ./${repo_root}/zsh/bin/run_ollama;
|
||||
executable = true;
|
||||
};
|
||||
"/bin/age-op" = {
|
||||
source = ./${repo_root}/bin/age-op;
|
||||
executable = true;
|
||||
};
|
||||
};
|
||||
xdg.configFile = {
|
||||
"1Password/ssh/agent.toml".text = ''
|
||||
|
|
|
@ -57,6 +57,7 @@ in {
|
|||
pdm
|
||||
python312Packages.pipx
|
||||
poetry
|
||||
rage
|
||||
ripgrep
|
||||
rsync
|
||||
scriptisto
|
||||
|
|
|
@ -76,6 +76,8 @@ in {
|
|||
EDITOR = "nvim";
|
||||
RPROMPT = "' '"; # Fixes a side-effect of the vi-mode oh-my-zsh plugin
|
||||
KEYTIMEOUT = 1;
|
||||
AGE = "rage";
|
||||
AGE_KEYPATH = "op://Jellyfish/age-key";
|
||||
};
|
||||
shellAliases = let
|
||||
platformSpecificAliases =
|
||||
|
|
Loading…
Add table
Reference in a new issue