#!/bin/bash _SQLITE=$( which sqlite3 2>&1 ) if [ $? -ne 0 ]; then echo "sqlite3 is missing" >&2 return 1 fi echo "test" | openssl enc -e -aes-256-cbc -pbkdf2 -k test 2>/dev/null > /dev/null if [ $? -ne 0 ]; then echo "openssl does not exists nor support pbkdf2" >&2 return 1 fi _AUTH_DB=$RBASH_HOME/keystore.db _AUTH_SECRET= _KSTORE_DEF_PROP=${_KSTORE_DEF_PROP:-val} _KSTORE_DEC_FAILED="$RBASH_HOME/.decypt-failed" _KSTORE_TABLE=store _KSTORE_GPG_RCPT="penguin@rbash.io" : "${_KSTORE_OPENSSL_ITER:=1000000}" function kstore { case $1 in add) shift; _kstoreadd "$@" ;; del) shift; _kstoredel "$@" ;; get) shift; _kstoreget "$@" ;; list) shift; _kstorelist "$@" ;; query) shift; _kstorequery "$@" ;; update) shift; _kstoreupdate "$@" ;; search) shift; _kstoresearch $@ ;; secret) shift; _kstoresecret $@ ;; upload) shift; _kstoreupload $@ ;; *) __func_head "add [key] [value|file|-] [prop, default: $_KSTORE_DEF_PROP]" __func_help "update [key] [value|file|-] [prop, default: $_KSTORE_DEF_PROP]" __func_help "upload [DIR]" __func_help "get [key] [prop, default: $_KSTORE_DEF_PROP]" __func_help "del [key] [prop, default: $_KSTORE_DEF_PROP]" __func_help "list" __func_help "search [key] [prop]" __func_help "secret ..." __func_help "upload s3-compatible-provider-name" __func_help "query SQL" return 1 ;; esac return $? } function _kstore { local CUR=${COMP_WORDS[COMP_CWORD]} local SCOPE=${COMP_WORDS[COMP_CWORD-1]} local t COMPREPLY=() case "$SCOPE" in kstore) COMPREPLY=( $(compgen -W "add del get list query update upload search secret" -- $CUR) ) return ;; get|update) COMPREPLY=( $(compgen -W "$( _kstorequery "SELECT DISTINCT key FROM $_KSTORE_TABLE" )" -- $CUR) ) ;; upload) COMPREPLY=( $(compgen -f -- "$CUR") ) ;; esac t=${COMP_WORDS[COMP_CWORD-2]} case "$t" in get|update) t=`_kstorequote "$SCOPE"` COMPREPLY=( $(compgen -W "$( _kstorequery "SELECT DISTINCT prop FROM $_KSTORE_TABLE WHERE key = '$t'" )" -- $CUR) ) ;; esac } complete -F _kstore kstore function _kstoresecret { case "$1" in clear) shift; _kstoresecret-clear "$@" ;; config) shift; _kstoresecret-config "$@" ;; change) shift; _kstoresecret-change "$@" ;; *) __func_head "clear" __func_help "config" __func_help "change" ;; esac } function _kstoreinit { if [ ! -f "$_AUTH_DB" ]; then cat <<___SQL___ | $_SQLITE "$_AUTH_DB" CREATE TABLE IF NOT EXISTS $_KSTORE_TABLE ( key TEXT NOT NULL , prop TEXT NOT NULL , data TEXT , PRIMARY KEY( key ASC, prop ASC ) ); ___SQL___ chmod 600 "$_AUTH_DB" fi kstore secret config } function _kstorequote { echo -n "$1" | sed -e "s/'/''/g" } function _kstoreenc { if [ -z "$_AUTH_SECRET" ]; then echo "Secret key is not set yet" >&2 return 1 fi if [ "$1" = "-" ]; then openssl enc \ -e \ -aes-256-cbc \ -pbkdf2 \ -iter "$_KSTORE_OPENSSL_ITER" \ -md sha256 \ -salt \ -a \ -A \ -pass fd:3 \ 3<<<"$_AUTH_SECRET" elif [ -f "$1" ]; then openssl enc \ -e \ -aes-256-cbc \ -pbkdf2 \ -iter "$_KSTORE_OPENSSL_ITER" \ -md sha256 \ -salt \ -a \ -A \ -in "$1" \ -pass fd:3 \ 3<<<"$_AUTH_SECRET" else printf "%s" "$1" | openssl enc \ -e \ -aes-256-cbc \ -pbkdf2 \ -iter "$_KSTORE_OPENSSL_ITER" \ -md sha256 \ -salt \ -a \ -A \ -pass fd:3 \ 3<<<"$_AUTH_SECRET" fi } function _kstoredec { if [ -z "$_AUTH_SECRET" ]; then echo "Secret key is not set yet" >&2 return 1 fi openssl enc \ -d \ -aes-256-cbc \ -pbkdf2 \ -iter "$_KSTORE_OPENSSL_ITER" \ -md sha256 \ -a \ -A \ -pass fd:3 \ 3<<<"$_AUTH_SECRET" _code=$? if [ "$_code" -ne 0 ]; then [ -n "$_KSTORE_DEC_FAILED" ] && touch "$_KSTORE_DEC_FAILED" return "$_code" fi } function _kstoresecret-auto { case $OSTYPE in darwin*) _kstoresecret-macos "$@" ;; cygwin) _kstoresecret-cygwin "$@" ;; linux*) _kstoresecret-linux "$@" ;; *) echo "$OSTYPE is not supported yet" >&2 ;; esac } function _kstoresecret-linux-ensure-gpg-key { local _GNUPGHOME _FPR if ! command -v gpg >/dev/null 2>&1; then echo "gpg is required" >&2 return 1 fi _GNUPGHOME="${GNUPGHOME:-$HOME/.gnupg}" mkdir -p "$_GNUPGHOME" || return 1 chmod 700 "$_GNUPGHOME" 2>/dev/null || true if [ -n "${_KSTORE_GPG_RCPT:-}" ]; then _FPR=$( gpg --batch --with-colons --list-secret-keys "$_KSTORE_GPG_RCPT" 2>/dev/null | awk -F: '$1 == "fpr" { print $10; exit }' ) if [ -n "$_FPR" ]; then export _KSTORE_GPG_RCPT="$_FPR" return 0 fi fi _FPR=$( gpg --batch --with-colons --list-secret-keys 2>/dev/null | awk -F: '$1 == "fpr" { print $10; exit }' ) if [ -n "$_FPR" ]; then export _KSTORE_GPG_RCPT="$_FPR" return 0 fi return 1 } function _kstoresecret-linux-generate-gpg-key { local _SECRET _UID _TMP _FPR _CODE _SECRET="$1" _UID="${KSTORE_GPG_UID:-rbash kstore }" if [ -z "$_SECRET" ]; then echo "empty local unlock secret" >&2 return 1 fi _TMP=$(mktemp) || return 1 chmod 600 "$_TMP" 2>/dev/null || true cat > "$_TMP" </dev/null | awk -F: '$1 == "fpr" { print $10; exit }' ) if [ -z "$_FPR" ]; then echo "failed to find generated GPG key fingerprint" >&2 return 1 fi export _KSTORE_GPG_RCPT="$_FPR" } function _kstoresecret-linux { local _SECRET_FILE _RECIPIENT _TMP _A _SECRET _SECRET2 _SECRET_FILE="${RBASH_HOME:-$HOME/.rbash}/keystore.secret.gpg" if ! _kstoresecret-linux-ensure-gpg-key; then read -rsp "Enter new local kstore unlock secret: " _SECRET echo >&2 read -rsp "Confirm new local kstore unlock secret: " _SECRET2 echo >&2 if [ -z "$_SECRET" ]; then echo "empty local unlock secret" >&2 unset _SECRET _SECRET2 return 1 fi if [ "$_SECRET" != "$_SECRET2" ]; then echo "local unlock secrets do not match" >&2 unset _SECRET _SECRET2 return 1 fi _kstoresecret-linux-generate-gpg-key "$_SECRET" || { unset _SECRET _SECRET2 return 1 } unset _SECRET _SECRET2 fi case "$1" in get) if [ ! -f "$_SECRET_FILE" ]; then return 1 fi _A=$( gpg --quiet --decrypt "$_SECRET_FILE" ) || { echo "failed to decrypt $_SECRET_FILE" >&2 return 1 } if [ -z "$_A" ]; then echo "decrypted _AUTH_SECRET is empty: $_SECRET_FILE" >&2 return 1 fi _AUTH_SECRET="$_A" unset _A ;; set) if [ -z "$_AUTH_SECRET" ]; then echo "_AUTH_SECRET is not loaded; cannot create $_SECRET_FILE" >&2 return 1 fi _RECIPIENT="$_KSTORE_GPG_RCPT" mkdir -p "${RBASH_HOME:-$HOME/.rbash}" || return 1 chmod 700 "${RBASH_HOME:-$HOME/.rbash}" 2>/dev/null || true _TMP=$(mktemp) || return 1 if printf "%s" "$_AUTH_SECRET" | gpg \ --quiet \ --batch \ --yes \ --encrypt \ --recipient "$_RECIPIENT" \ --output "$_TMP" then chmod 600 "$_TMP" 2>/dev/null || true else rm -f "$_TMP" echo "failed to create $_SECRET_FILE" >&2 return 1 fi _A=$( gpg --quiet --decrypt "$_TMP" ) || { rm -f "$_TMP" echo "failed to verify $_SECRET_FILE" >&2 return 1 } if [ "$_A" != "$_AUTH_SECRET" ]; then rm -f "$_TMP" unset _A echo "verification mismatch for $_SECRET_FILE" >&2 return 1 fi unset _A mv "$_TMP" "$_SECRET_FILE" ;; del) rm -f "$_SECRET_FILE" ;; *) echo "Unknown action: $1" >&2 return 1 ;; esac } function _kstoresecret-cygwin { local _A which kstorecred 2>/dev/null > /dev/null if [ $? -ne 0 ]; then _kstoredl-kstorecred fi case $1 in get) _A=$(kstorecred get) if [ $? -ne 0 ]; then return 1 fi _AUTH_SECRET="$_A" ;; set) printf "%s" "$_AUTH_SECRET" | kstorecred set ;; del) kstorecred del ;; *) echo "Unknown action: $1" >&2 return 1 ;; esac } function _kstoresecret-macos { local _A case "$1" in get) _A=$(security find-generic-password -a default -s rbash-kstore -w 2>/dev/null) || return 1 _AUTH_SECRET="$_A" ;; set) security add-generic-password -a default -s rbash-kstore -w "$_AUTH_SECRET" -U ;; del) security delete-generic-password -a default -s rbash-kstore 2>/dev/null ;; *) echo "Unknown action: $1" >&2 return 1 ;; esac } function _kstoresecret-config { local _CONFIRM if [ -f "$_KSTORE_DEC_FAILED" ]; then rm $_KSTORE_DEC_FAILED _AUTH_SECRET= fi if [ -z "$_AUTH_SECRET" ]; then _kstoresecret-auto get if [ -n "$_AUTH_SECRET" ]; then return 0 fi read -sp "Enter passphrase: " _AUTH_SECRET echo if [ -z "$_AUTH_SECRET" ]; then echo "Passphrase cannot be empty" >&2 return 1 fi case $OSTYPE in cygwin|linux|darwin*) read -p "Save this password to OS's keystore? (y/n): " _CONFIRM if [ "$_CONFIRM" != "y" ]; then return 0 fi _kstoresecret-auto set ;; esac fi } function _kstoresecret-change { _kstoresecret-config local _NEW_SECRET i _key _prop _O_SECRET _BAK read -sp "Enter the new passphrase: " _NEW_SECRET echo read -sp "Enter the passphrase again: " i echo if [ "$i" != "$_NEW_SECRET" ]; then echo "Passphrase mismatched" >&2 return 1 fi _BAK=$( mktemp ) cp "$_AUTH_DB" "$_BAK" echo "Backed up at $_BAK" _O_SECRET="$_AUTH_SECRET" for i in `$_SQLITE -list "$_AUTH_DB" "SELECT _ROWID_ FROM $_KSTORE_TABLE;"`; do _AUTH_SECRET=$_O_SECRET _key=`$_SQLITE -list "$_AUTH_DB" "SELECT key FROM $_KSTORE_TABLE WHERE _ROWID_ = $i;"` _key=`_kstorequote "$_key"` _prop=`$_SQLITE -list "$_AUTH_DB" "SELECT prop FROM $_KSTORE_TABLE WHERE _ROWID_ = $i;"` _prop=`_kstorequote "$_prop"` _val=`kstore get "$_key" "$_prop"` if [ $? -ne 0 ]; then echo "Current passphrase is incorrect?" >&2 return 1 fi _AUTH_SECRET=$_NEW_SECRET echo Updating: [$_key] [$_prop] kstore update "$_key" "$_val" "$_prop" done _kstoresecret-auto get if [ -n "$_AUTH_SECRET" ]; then _kstoresecret-auto del 2>&1 > /dev/null _AUTH_SECRET=$_NEW_SECRET _kstoresecret-auto set if [ $? -eq 0 ]; then echo "New passphrase saved in OS's keystore." fi fi _AUTH_SECRET=$_NEW_SECRET } function _kstoreupload { if [ -z "$1" ]; then __func_head "[DIR]" return 1 fi _kstoreinit || return 1 which arch-upload-aws4 2>&1 > /dev/null if [ $? -ne 0 ]; then _kstoreinit-s3au fi local _TARGET _FNAME _TEMP _TARGET="$1" if [ ! -r "$_TARGET" ]; then echo "\"$_TARGET\" is not readable" return 1 fi _FNAME=$( basename "$_TARGET" ) _TEMP="$( mktemp ).tar.gz" tar zcf "$_TEMP" "$_TARGET" _kstoreenc "$_TEMP" > "${_TEMP}.enc" echo "Size: $(du -sh "${_TEMP}.enc")" arch-upload-aws4 "uploads/$_TARGET.enc" "${_TEMP}.enc" rm "${_TEMP}" rm "${_TEMP}.enc" } function _kstoresecret-clear { unset _AUTH_SECRET _kstoresecret-auto del } function _kstoreupdate { if [ -z "$1" ]; then __func_head "[key] [value|file|-] [prop, default: $_KSTORE_DEF_PROP]" return 1 fi _kstoreinit || return 1 local _key _val _prop _key=`_kstorequote "$1"` _val=`_kstoreenc "$2"` _val=`_kstorequote "$_val"` _prop=`_kstorequote "${3:-$_KSTORE_DEF_PROP}"` _cond="key = '$_key' AND prop = '$_prop'" $_SQLITE "$_AUTH_DB" "UPDATE $_KSTORE_TABLE SET data = '$_val' WHERE $_cond;" } function _kstoreadd { if [ -z "$1" ]; then __func_head "[key] [value|file|-] [prop, default: $_KSTORE_DEF_PROP]" return 1 fi _kstoreinit || return 1 local _key _val _prop _key=`_kstorequote "$1"` _val=`_kstoreenc "$2"` _val=`_kstorequote "$_val"` _prop=`_kstorequote "${3:-$_KSTORE_DEF_PROP}"` $_SQLITE "$_AUTH_DB" \ "INSERT INTO $_KSTORE_TABLE ( key, prop, data ) VALUES( '$_key', '$_prop', '$_val' );" } function _kstoreget { if [ -z "$1" ]; then __func_head "[key] [prop, default: $_KSTORE_DEF_PROP]" return 1 fi _kstoreinit || return 1 local _key _prop _cond _key=`_kstorequote "$1"` _prop=`_kstorequote "${2:-$_KSTORE_DEF_PROP}"` _cond="key = '$_key' AND prop = '$_prop'" $_SQLITE "$_AUTH_DB" "SELECT 1111 FROM $_KSTORE_TABLE WHERE $_cond;" | grep -q 1111 if [ $? -eq 0 ]; then $_SQLITE -list "$_AUTH_DB" "SELECT ( data ) FROM $_KSTORE_TABLE WHERE $_cond;" | _kstoredec else echo "\"$1\" not found (prop: $_prop)" >&2 return 1 fi } function _kstoredel { if [ -z "$1" ]; then __func_head "[key] [prop, default: $_KSTORE_DEF_PROP]" return 1 fi _kstoreinit || return 1 local _CONFIRM _key _prop _cond _key=`_kstorequote "$1"` _prop=`_kstorequote "${2:-$_KSTORE_DEF_PROP}"` _cond="key = '$_key' AND prop = '$_prop'" $_SQLITE "$_AUTH_DB" "SELECT 1111 FROM $_KSTORE_TABLE WHERE $_cond;" | grep -q 1111 if [ $? -eq 0 ]; then $_SQLITE "$_AUTH_DB" "SELECT * FROM $_KSTORE_TABLE WHERE $_cond;" echo read -p "Delete this entry (yes/no)? " _CONFIRM if [ "$_CONFIRM" == "yes" ]; then $_SQLITE "$_AUTH_DB" "DELETE FROM $_KSTORE_TABLE WHERE $_cond;" if [ $? -eq 0 ]; then echo "deleted" fi else echo "action canceled" fi else echo "\"$1\" not found (prop: $_prop)" >&2 fi } function _kstoredl-s3au { local p tmp _NAME p="https://sgit.k8s.astropenguin.net/penguin/s3-arch-utils/raw/master" _CSUM=$1 _NAME=$2 tmp=$( mktemp ) rbash_download "$p/$_NAME" > $tmp sha256sum $tmp | grep -q "$_CSUM" if [ $? -eq 0 ]; then _NAME=$( basename "${_NAME//_/-}" .sh ) mv $tmp "$RBASH_BIN/$_NAME" chmod +x "$RBASH_BIN/$_NAME" echo "> $RBASH_BIN/$_NAME" else echo "$_NAME: Signature mismatch" return 1 fi } function _kstoredl-kstorecred { local p tmp _NAME _NAME="kstorecred" p="https://penguins-workspace.s3.ap-southeast-1.astropenguin.net/keystore/kstorecred.exe" _CSUM="031b4474b8eb8deafbb96df73b79f8b654fd3c2209f041fd34cb6f494791bcd4" tmp=$( mktemp ) rbash_download "$p" > $tmp sha256sum $tmp | grep -q "$_CSUM" if [ $? -eq 0 ]; then mv $tmp "$RBASH_BIN/$_NAME" chmod +x "$RBASH_BIN/$_NAME" echo "> $RBASH_BIN/$_NAME" else echo "$_NAME: Signature mismatch" return 1 fi } function _kstoreinit-s3au { _kstoredl-s3au "574d106cdced150fa6e04a9437d356d8688035cb2c63a045fa0d4ead8b3ec941" "arch_delete_aws4.sh" || return 1 _kstoredl-s3au "0be740bced7b698d3b23931bf7bd9a77aa22eb3c81f6e9205be4cce4eded0d11" "arch_delete_many_aws4.sh" || return 1 _kstoredl-s3au "4c2a3d67fd97f1b54847424a596f08e2901ee183301f268e2ffd92a4f0ccef32" "arch_download_aws4.sh" || return 1 _kstoredl-s3au "0bee6f925a41f496f66654062b49ea2a1cc55d877f4617d56bf91244aaf0be51" "arch_getacl_aws4.sh" || return 1 _kstoredl-s3au "4eb3aba45d9d20b15b8aab575955084bf525be62661e5b7de2bdeac0260ec9fc" "arch_list_aws4.sh" || return 1 _kstoredl-s3au "e90d197e4f965f88c30f8ff4b0a43ee4b5d656032781f1647770637fff0b9481" "arch_upload_aws4.sh" || return 1 } function _kstoreupload-db { local _T _kstoreinit || return 1 _T=$(date +%Y%m%d%H%M%S) echo "$_T" > "$RBASH_HOME/keystore.latest" which arch-upload-aws4 >/dev/null 2>&1 if [ $? -ne 0 ]; then _kstoreinit-s3au || return 1 fi _kstoreenc "$_AUTH_DB" > "$RBASH_HOME/$_T.enc" || return 1 chmod 600 "$RBASH_HOME/$_T.enc" arch-upload-aws4 "keystore/$_T.enc" "$RBASH_HOME/$_T.enc" || return 1 arch-upload-aws4 "keystore/latest" "$RBASH_HOME/keystore.latest" || return 1 rm -f "$RBASH_HOME/$_T.enc" "$RBASH_HOME/keystore.latest" } function _kstoredownload-db { local _T _TMP _CONFIRM _OLD_PIPEFAIL kstore secret config || return 1 which arch-download-aws4 >/dev/null 2>&1 if [ $? -ne 0 ]; then _kstoreinit-s3au || return 1 fi _T=$(arch-download-aws4 "keystore/latest") || return 1 _TMP=$(mktemp) || return 1 _OLD_PIPEFAIL=$(set +o | grep pipefail) set -o pipefail if ! arch-download-aws4 "keystore/$_T.enc" | _kstoredec > "$_TMP"; then eval "$_OLD_PIPEFAIL" rm -f "$_TMP" echo "Download/decrypt failed. Incorrect bucket? ($ARCH_S3_BUCKET_URL)" >&2 return 1 fi eval "$_OLD_PIPEFAIL" if [ "$($_SQLITE "$_TMP" "PRAGMA integrity_check;")" != "ok" ]; then rm -f "$_TMP" echo "Downloaded DB failed SQLite integrity check" >&2 return 1 fi if ! $_SQLITE "$_TMP" "SELECT COUNT(*) FROM $_KSTORE_TABLE;" >/dev/null; then rm -f "$_TMP" echo "Downloaded DB does not look like a kstore DB" >&2 return 1 fi chmod 600 "$_TMP" if [ -f "$_AUTH_DB" ]; then read -p "Replace existing db? (yes/no): " _CONFIRM if [ "$_CONFIRM" = "yes" ]; then _T="$_AUTH_DB.old.$(date +%Y%m%d%H%M%S)" mv "$_AUTH_DB" "$_T" mv "$_TMP" "$_AUTH_DB" echo "Original copy: $_T" else echo "action canceled" rm -f "$_TMP" fi else mv "$_TMP" "$_AUTH_DB" fi } function _kstoresearch { local _termk _termp _cond _termk=`_kstorequote "$1"` _termp=`_kstorequote "$2"` _cond="key LIKE '%$_termk%' AND prop LIKE '%$_termp%'" $_SQLITE -header -column "$_AUTH_DB" "SELECT key, prop, length( data ) FROM $_KSTORE_TABLE WHERE $_cond;" } function _kstorelist { $_SQLITE -header -column "$_AUTH_DB" "SELECT key, prop, length( data ) FROM $_KSTORE_TABLE;" } function _kstorequery { $_SQLITE "$_AUTH_DB" "$@" }